diff options
161 files changed, 4147 insertions, 954 deletions
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java index 3c361d772d3d..95730e836056 100644 --- a/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java +++ b/apct-tests/perftests/core/src/android/graphics/perftests/CanvasPerfTest.java @@ -122,6 +122,8 @@ public class CanvasPerfTest { Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true) .recycle(); } + source.recycle(); + Runtime.getRuntime().gc(); } @Test @@ -141,6 +143,8 @@ public class CanvasPerfTest { Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true) .recycle(); } + source.recycle(); + Runtime.getRuntime().gc(); } @Test @@ -158,5 +162,7 @@ public class CanvasPerfTest { Bitmap.createScaledBitmap(source, source.getWidth() / 2, source.getHeight() / 2, true) .recycle(); } + source.recycle(); + Runtime.getRuntime().gc(); } } diff --git a/core/api/current.txt b/core/api/current.txt index 20b9b0e2331d..671956d54d74 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12447,6 +12447,7 @@ package android.content.pm { method @NonNull public android.content.pm.PackageInstaller.Session openSession(int) throws java.io.IOException; method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback); method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler); + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalState(@NonNull android.content.pm.PackageInstaller.UnarchivalState) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; @@ -12676,6 +12677,14 @@ package android.content.pm { field public static final int USER_ACTION_UNSPECIFIED = 0; // 0x0 } + @FlaggedApi("android.content.pm.archiving") public static final class PackageInstaller.UnarchivalState { + method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createGenericErrorState(int); + method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createInsufficientStorageState(int, long, @Nullable android.app.PendingIntent); + method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createNoConnectivityState(int); + method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createOkState(int); + method @NonNull public static android.content.pm.PackageInstaller.UnarchivalState createUserActionRequiredState(int, @NonNull android.app.PendingIntent); + } + public class PackageItemInfo { ctor public PackageItemInfo(); ctor public PackageItemInfo(android.content.pm.PackageItemInfo); @@ -24261,7 +24270,7 @@ package android.media { method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String); method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers(); method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context); - method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.os.UserHandle); + method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String, @NonNull android.os.UserHandle); method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") @Nullable public android.media.RouteListingPreference getRouteListingPreference(); method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes(); method @NonNull public android.media.MediaRouter2.RoutingController getSystemController(); diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 7370fc36c23e..5b044f616487 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -119,7 +119,7 @@ interface IActivityClientController { oneway void setShowWhenLocked(in IBinder token, boolean showWhenLocked); oneway void setInheritShowWhenLocked(in IBinder token, boolean setInheritShownWhenLocked); - oneway void setTurnScreenOn(in IBinder token, boolean turnScreenOn); + void setTurnScreenOn(in IBinder token, boolean turnScreenOn); oneway void setAllowCrossUidActivitySwitchFromBelow(in IBinder token, boolean allowed); oneway void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle); oneway void overrideActivityTransition(IBinder token, boolean open, int enterAnim, int exitAnim, diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 63f37f150d33..36b03c1b1f48 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -261,6 +261,13 @@ public class WallpaperManager { public static final String COMMAND_GOING_TO_SLEEP = "android.wallpaper.goingtosleep"; /** + * Command for {@link #sendWallpaperCommand}: reported when a physical display switch event + * happens, e.g. fold and unfold. + * @hide + */ + public static final String COMMAND_DISPLAY_SWITCH = "android.wallpaper.displayswitch"; + + /** * Command for {@link #sendWallpaperCommand}: reported when the wallpaper that was already * set is re-applied by the user. * @hide diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 35ce10223aa6..b3ecd92c56c9 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -55,3 +55,10 @@ flag { description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped." bug: "293441361" } + +flag { + name: "default_sms_personal_app_suspension_fix_enabled" + namespace: "enterprise" + description: "Exempt the default sms app of the context user for suspension when calling setPersonalAppsSuspended" + bug: "309183330" +} diff --git a/core/java/android/app/backup/BackupHelperWithLogger.java b/core/java/android/app/backup/BackupHelperWithLogger.java new file mode 100644 index 000000000000..1a59a5302f07 --- /dev/null +++ b/core/java/android/app/backup/BackupHelperWithLogger.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.backup; + +import android.os.ParcelFileDescriptor; + +/** + * Utility class for writing BackupHelpers with added logging capabilities. + * Used for passing a logger object to Helper in key shared backup agents + * + * @hide + */ +public abstract class BackupHelperWithLogger implements BackupHelper { + private BackupRestoreEventLogger mLogger; + private boolean mIsLoggerSet = false; + + public abstract void writeNewStateDescription(ParcelFileDescriptor newState); + + public abstract void restoreEntity(BackupDataInputStream data); + + public abstract void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState); + + /** + * Gets the logger so that the backuphelper can log success/error for each datatype handled + */ + public BackupRestoreEventLogger getLogger() { + return mLogger; + } + + /** + * Allow the shared backup agent to pass a logger to each of its backup helper + */ + public void setLogger(BackupRestoreEventLogger logger) { + mLogger = logger; + mIsLoggerSet = true; + } + + /** + * Allow the helper to check if its shared backup agent has passed a logger + */ + public boolean isLoggerSet() { + return mIsLoggerSet; + } +} diff --git a/core/java/android/app/backup/BlobBackupHelper.java b/core/java/android/app/backup/BlobBackupHelper.java index 82d0a94ce0da..a55ff4899296 100644 --- a/core/java/android/app/backup/BlobBackupHelper.java +++ b/core/java/android/app/backup/BlobBackupHelper.java @@ -39,7 +39,7 @@ import java.util.zip.InflaterInputStream; * * @hide */ -public abstract class BlobBackupHelper implements BackupHelper { +public abstract class BlobBackupHelper extends BackupHelperWithLogger { private static final String TAG = "BlobBackupHelper"; private static final boolean DEBUG = false; diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 22926febfb2c..c4bf18d70242 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -738,7 +738,7 @@ public class PackageInstaller { /** * The set of error types that can be set for - * {@link #reportUnarchivalStatus(int, int, PendingIntent)}. + * {@link #reportUnarchivalState}. * * @hide */ @@ -2421,6 +2421,7 @@ public class PackageInstaller { * facilitate the unarchival flow (e.g. user needs to log in). * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists */ + // TODO(b/314960798) Remove old API once it's unused @RequiresPermission(anyOf = { Manifest.permission.INSTALL_PACKAGES, Manifest.permission.REQUEST_INSTALL_PACKAGES}) @@ -2438,6 +2439,30 @@ public class PackageInstaller { } } + /** + * Reports the state of an unarchival to the system. + * + * @see UnarchivalState for the different state options. + * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists + */ + @RequiresPermission(anyOf = { + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.REQUEST_INSTALL_PACKAGES}) + @FlaggedApi(Flags.FLAG_ARCHIVING) + public void reportUnarchivalState(@NonNull UnarchivalState unarchivalState) + throws PackageManager.NameNotFoundException { + Objects.requireNonNull(unarchivalState); + try { + mInstaller.reportUnarchivalStatus(unarchivalState.getUnarchiveId(), + unarchivalState.getStatus(), unarchivalState.getRequiredStorageBytes(), + unarchivalState.getUserActionIntent(), new UserHandle(mUserId)); + } catch (ParcelableException e) { + e.maybeRethrow(PackageManager.NameNotFoundException.class); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + // (b/239722738) This class serves as a bridge between the PackageLite class, which // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java) // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or @@ -4741,10 +4766,10 @@ public class PackageInstaller { codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java", inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.PackageInstaller.PreapprovalDetails> CREATOR\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\npublic @java.lang.Override int describeContents()\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate @android.annotation.NonNull java.lang.String mPackageName\nprivate long mBuilderFieldsSet\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(android.graphics.Bitmap)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(java.lang.CharSequence)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(android.icu.util.ULocale)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(java.lang.String)\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.PreapprovalDetails build()\nprivate void checkNotUsed()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true)") + @Deprecated private void __metadata() {} - //@formatter:on // End of generated code @@ -5135,13 +5160,188 @@ public class PackageInstaller { codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java", inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mDeviceIdleRequired\nprivate final boolean mAppNotForegroundRequired\nprivate final boolean mAppNotInteractingRequired\nprivate final boolean mAppNotTopVisibleRequired\nprivate final boolean mNotInCallRequired\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mDeviceIdleRequired\nprivate boolean mAppNotForegroundRequired\nprivate boolean mAppNotInteractingRequired\nprivate boolean mAppNotTopVisibleRequired\nprivate boolean mNotInCallRequired\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setDeviceIdleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotForegroundRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotInteractingRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setAppNotTopVisibleRequired()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder setNotInCallRequired()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genEqualsHashCode=true)") + @Deprecated private void __metadata() {} - //@formatter:on // End of generated code } + /** + * Used to communicate the unarchival state in {@link #reportUnarchivalState}. + */ + @FlaggedApi(Flags.FLAG_ARCHIVING) + public static final class UnarchivalState { + + /** + * The caller is able to facilitate the unarchival for the given {@code unarchiveId}. + * + * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE + * broadcast with EXTRA_UNARCHIVE_ID. + */ + @NonNull + public static UnarchivalState createOkState(int unarchiveId) { + return new UnarchivalState(unarchiveId, UNARCHIVAL_OK, /* requiredStorageBytes= */ -1, + /* userActionIntent= */ null); + } + + /** + * User action is required before commencing with the unarchival for the given + * {@code unarchiveId}. E.g., this could be used if it's necessary for the user to sign-in + * first. + * + * @param unarchiveId the ID provided by the system as part of the + * intent.action.UNARCHIVE + * broadcast with EXTRA_UNARCHIVE_ID. + * @param userActionIntent optional intent to start a follow up action required to + * facilitate the unarchival flow (e.g. user needs to log in). + */ + @NonNull + public static UnarchivalState createUserActionRequiredState(int unarchiveId, + @NonNull PendingIntent userActionIntent) { + Objects.requireNonNull(userActionIntent); + return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_USER_ACTION_NEEDED, + /* requiredStorageBytes= */ -1, userActionIntent); + } + + /** + * There is not enough storage to start the unarchival for the given {@code unarchiveId}. + * + * @param unarchiveId the ID provided by the system as part of the + * intent.action.UNARCHIVE + * broadcast with EXTRA_UNARCHIVE_ID. + * @param requiredStorageBytes ff the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this + * field should be set to specify how many additional bytes of + * storage are required to unarchive the app. + * @param userActionIntent can optionally be set to provide a custom storage-clearing + * action. + */ + @NonNull + public static UnarchivalState createInsufficientStorageState(int unarchiveId, + long requiredStorageBytes, @Nullable PendingIntent userActionIntent) { + return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, + requiredStorageBytes, userActionIntent); + } + + /** + * The device has no data connectivity and unarchival cannot be started for the given + * {@code unarchiveId}. + * + * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE + * broadcast with EXTRA_UNARCHIVE_ID. + */ + @NonNull + public static UnarchivalState createNoConnectivityState(int unarchiveId) { + return new UnarchivalState(unarchiveId, UNARCHIVAL_ERROR_NO_CONNECTIVITY, + /* requiredStorageBytes= */ -1,/* userActionIntent= */ null); + } + + /** + * Generic error state for all cases that are not covered by other methods in this class. + * + * @param unarchiveId the ID provided by the system as part of the intent.action.UNARCHIVE + * broadcast with EXTRA_UNARCHIVE_ID. + */ + @NonNull + public static UnarchivalState createGenericErrorState(int unarchiveId) { + return new UnarchivalState(unarchiveId, UNARCHIVAL_GENERIC_ERROR, + /* requiredStorageBytes= */ -1,/* userActionIntent= */ null); + } + + + /** + * The ID provided by the system as part of the intent.action.UNARCHIVE broadcast with + * EXTRA_UNARCHIVE_ID. + */ + private final int mUnarchiveId; + + /** Used for the system to provide the user with necessary follow-up steps or errors. */ + @UnarchivalStatus + private final int mStatus; + + /** + * If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field should be set to specify + * how many additional bytes of storage are required to unarchive the app. + */ + private final long mRequiredStorageBytes; + + /** + * Optional intent to start a follow up action required to facilitate the unarchival flow + * (e.g., user needs to log in). + */ + @Nullable + private final PendingIntent mUserActionIntent; + + /** + * Creates a new UnarchivalState. + * + * @param unarchiveId The ID provided by the system as part of the + * intent.action.UNARCHIVE broadcast with + * EXTRA_UNARCHIVE_ID. + * @param status Used for the system to provide the user with necessary + * follow-up steps or errors. + * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this + * field should be set to specify + * how many additional bytes of storage are required to + * unarchive the app. + * @param userActionIntent Optional intent to start a follow up action required to + * facilitate the unarchival flow + * (e.g,. user needs to log in). + * @hide + */ + private UnarchivalState( + int unarchiveId, + @UnarchivalStatus int status, + long requiredStorageBytes, + @Nullable PendingIntent userActionIntent) { + this.mUnarchiveId = unarchiveId; + this.mStatus = status; + com.android.internal.util.AnnotationValidations.validate( + UnarchivalStatus.class, null, mStatus); + this.mRequiredStorageBytes = requiredStorageBytes; + this.mUserActionIntent = userActionIntent; + } + + /** + * The ID provided by the system as part of the intent.action.UNARCHIVE broadcast with + * EXTRA_UNARCHIVE_ID. + * + * @hide + */ + int getUnarchiveId() { + return mUnarchiveId; + } + + /** + * Used for the system to provide the user with necessary follow-up steps or errors. + * + * @hide + */ + @UnarchivalStatus int getStatus() { + return mStatus; + } + + /** + * If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field should be set to specify + * how many additional bytes of storage are required to unarchive the app. + * + * @hide + */ + long getRequiredStorageBytes() { + return mRequiredStorageBytes; + } + + /** + * Optional intent to start a follow up action required to facilitate the unarchival flow + * (e.g. user needs to log in). + * + * @hide + */ + @Nullable PendingIntent getUserActionIntent() { + return mUserActionIntent; + } + } + } diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java index a4db733af013..bd74b0b9293c 100644 --- a/core/java/android/content/pm/overlay/OverlayPaths.java +++ b/core/java/android/content/pm/overlay/OverlayPaths.java @@ -49,6 +49,13 @@ public class OverlayPaths { public static class Builder { final OverlayPaths mPaths = new OverlayPaths(); + public Builder() {} + + public Builder(@NonNull OverlayPaths base) { + mPaths.mResourceDirs.addAll(base.getResourceDirs()); + mPaths.mOverlayPaths.addAll(base.getOverlayPaths()); + } + /** * Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}. */ diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 1a2be15b2e8d..76e0c259b38f 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -16,6 +16,7 @@ package android.service.wallpaper; +import static android.app.WallpaperManager.COMMAND_DISPLAY_SWITCH; import static android.app.WallpaperManager.COMMAND_FREEZE; import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.app.WallpaperManager.SetWallpaperFlags; @@ -153,6 +154,7 @@ public abstract class WallpaperService extends Service { static final boolean DEBUG = false; static final float MIN_PAGE_ALLOWED_MARGIN = .05f; private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64; + private static final long PRESERVE_VISIBLE_TIMEOUT_MS = 1000; private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute private static final @NonNull RectF LOCAL_COLOR_BOUNDS = new RectF(0, 0, 1, 1); @@ -165,6 +167,7 @@ public abstract class WallpaperService extends Service { private static final int MSG_UPDATE_SURFACE = 10000; private static final int MSG_VISIBILITY_CHANGED = 10010; + private static final int MSG_REFRESH_VISIBILITY = 10011; private static final int MSG_WALLPAPER_OFFSETS = 10020; private static final int MSG_WALLPAPER_COMMAND = 10025; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -248,6 +251,11 @@ public abstract class WallpaperService extends Service { */ private boolean mIsScreenTurningOn; boolean mReportedVisible; + /** + * This is used with {@link #PRESERVE_VISIBLE_TIMEOUT_MS} to avoid intermediate visibility + * changes if the display may be toggled in a short time, e.g. display switch. + */ + boolean mPreserveVisible; boolean mDestroyed; // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false // after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once @@ -1084,6 +1092,9 @@ public abstract class WallpaperService extends Service { if (pendingCount != 0) { out.print(prefix); out.print("mPendingResizeCount="); out.println(pendingCount); } + if (mPreserveVisible) { + out.print(prefix); out.print("mPreserveVisible=true"); + } synchronized (mLock) { out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset); out.print(" mPendingXOffset="); out.println(mPendingXOffset); @@ -1643,7 +1654,8 @@ public abstract class WallpaperService extends Service { ? false : mIWallpaperEngine.mInfo.supportsAmbientMode(); // Report visibility only if display is fully on or wallpaper supports ambient mode. - boolean visible = mVisible && (displayFullyOn || supportsAmbientMode); + final boolean visible = (mVisible && (displayFullyOn || supportsAmbientMode)) + || mPreserveVisible; if (DEBUG) { Log.v( TAG, @@ -2080,6 +2092,9 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { if (COMMAND_FREEZE.equals(cmd.action) || COMMAND_UNFREEZE.equals(cmd.action)) { updateFrozenState(/* frozenRequested= */ !COMMAND_UNFREEZE.equals(cmd.action)); + } else if (COMMAND_DISPLAY_SWITCH.equals(cmd.action)) { + handleDisplaySwitch(cmd.z == 1 /* startToSwitch */); + return; } result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z, cmd.extras, cmd.sync); @@ -2095,6 +2110,23 @@ public abstract class WallpaperService extends Service { } } + private void handleDisplaySwitch(boolean startToSwitch) { + if (startToSwitch && mReportedVisible) { + // The display may be off/on in a short time when the display is switching. + // Keep the visible state until onScreenTurnedOn or !startToSwitch is received, so + // the rendering thread can be active to redraw in time when receiving size change. + mPreserveVisible = true; + mCaller.removeMessages(MSG_REFRESH_VISIBILITY); + mCaller.sendMessageDelayed(mCaller.obtainMessage(MSG_REFRESH_VISIBILITY), + PRESERVE_VISIBLE_TIMEOUT_MS); + } else if (!startToSwitch && mPreserveVisible) { + // The switch is finished, so restore to actual visibility. + mPreserveVisible = false; + mCaller.removeMessages(MSG_REFRESH_VISIBILITY); + reportVisibility(false /* forceReport */); + } + } + private void updateFrozenState(boolean frozenRequested) { if (mIWallpaperEngine.mInfo == null // Procees the unfreeze command in case the wallaper became static while @@ -2638,6 +2670,10 @@ public abstract class WallpaperService extends Service { + ": " + message.arg1); mEngine.doVisibilityChanged(message.arg1 != 0); break; + case MSG_REFRESH_VISIBILITY: + mEngine.mPreserveVisible = false; + mEngine.reportVisibility(false /* forceReport */); + break; case MSG_UPDATE_SCREEN_TURNING_ON: if (DEBUG) { Log.v(TAG, diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index efae57c9946c..a11ac7cb48ad 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -94,6 +94,13 @@ flag { } flag { + name: "skip_accessibility_warning_dialog_for_trusted_services" + namespace: "accessibility" + description: "Skips showing the accessibility warning dialog for trusted services." + bug: "303511250" +} + +flag { namespace: "accessibility" name: "update_always_on_a11y_service" description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut." diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index c20b278f7eaa..7f5331b936e9 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -167,6 +167,11 @@ public class WindowTokenClient extends Binder { + ", reported config=" + currentConfig + ", updated config=" + newConfig); } + // Update display first. In case callers want to obtain display information( + // ex: DisplayMetrics) in #onConfigurationChanged callback. + if (displayChanged) { + context.updateDisplay(newDisplayId); + } if (shouldUpdateResources) { // TODO(ag/9789103): update resource manager logic to track non-activity tokens mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); @@ -195,9 +200,6 @@ public class WindowTokenClient extends Binder { } } } - if (displayChanged) { - context.updateDisplay(newDisplayId); - } } /** diff --git a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java index ce9ab82614d5..2ff62251d786 100644 --- a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java +++ b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java @@ -21,6 +21,7 @@ import android.accounts.AccountManager; import android.app.backup.BackupDataInputStream; import android.app.backup.BackupDataOutput; import android.app.backup.BackupHelper; +import android.app.backup.BackupHelperWithLogger; import android.content.ContentResolver; import android.content.Context; import android.content.SyncAdapterType; @@ -56,7 +57,7 @@ import java.util.Set; * sync settings are backed up as a JSON object containing all the necessary information for * restoring the sync settings later. */ -public class AccountSyncSettingsBackupHelper implements BackupHelper { +public class AccountSyncSettingsBackupHelper extends BackupHelperWithLogger { private static final String TAG = "AccountSyncSettingsBackupHelper"; private static final boolean DEBUG = false; diff --git a/core/res/res/drawable/autofill_half_sheet_divider.xml b/core/res/res/drawable/autofill_half_sheet_divider.xml new file mode 100644 index 000000000000..1a96c7dc263e --- /dev/null +++ b/core/res/res/drawable/autofill_half_sheet_divider.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copied from //frameworks/base/core/res/res/drawable/list_divider_material.xml. --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:tint="@color/foreground_material_light"> + <solid android:color="#1f000000" /> + <size + android:height="1dp" + android:width="1dp"/> +</shape>
\ No newline at end of file diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml index 27f8138ac5e3..ddedca2865c4 100644 --- a/core/res/res/layout/autofill_save.xml +++ b/core/res/res/layout/autofill_save.xml @@ -31,11 +31,11 @@ android:gravity="center_horizontal" android:orientation="vertical"> <ScrollView + android:id="@+id/autofill_sheet_scroll_view" android:layout_width="fill_parent" android:layout_height="0dp" android:fillViewport="true" - android:layout_weight="1" - android:layout_marginBottom="8dp"> + android:layout_weight="1"> <LinearLayout android:layout_marginStart="@dimen/autofill_save_outer_margin" android:layout_marginEnd="@dimen/autofill_save_outer_margin" @@ -66,16 +66,25 @@ android:layout_height="wrap_content" android:minHeight="0dp" android:visibility="gone"/> - + <View + android:id="@+id/autofill_sheet_scroll_view_space" + android:layout_width="match_parent" + android:layout_height="16dp"/> </LinearLayout> </ScrollView> + <View + android:id="@+id/autofill_sheet_divider" + android:layout_width="match_parent" + android:layout_height="1dp" + style="@style/AutofillHalfSheetDivider" /> + <com.android.internal.widget.ButtonBarLayout android:layout_width="match_parent" android:layout_height="48dp" android:layout_gravity="end" android:clipToPadding="false" - android:layout_marginTop="16dp" + android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton" android:orientation="horizontal" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9e1400641084..5e2aacdb24af 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4497,6 +4497,16 @@ <!-- URI for default Accessibility notification sound when to enable accessibility shortcut. --> <string name="config_defaultAccessibilityNotificationSound" translatable="false"></string> + <!-- Array of component names, each flattened to a string, for accessibility services that + can be enabled by the user without showing a warning prompt. These services must be + preinstalled. --> + <string-array translatable="false" name="config_trustedAccessibilityServices"> + <!-- + <item>com.example.package.first/com.example.class.FirstService</item> + <item>com.example.package.second/com.example.class.SecondService</item> + --> + </string-array> + <!-- Warning: This API can be dangerous when not implemented properly. In particular, escrow token must NOT be retrievable from device storage. In other words, either escrow token is not stored on device or its ciphertext is stored on device while @@ -6891,4 +6901,8 @@ <!-- Defines suitability of the built-in speaker route. Refer to {@link MediaRoute2Info} to see supported values. --> <integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer> + + <!-- Whether to show a percentage text next to the progressbar while preparing to update the + device --> + <bool name="config_showPercentageTextDuringRebootToUpdate">true</bool> </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 619ec31e37bc..22d028cb079e 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1515,6 +1515,11 @@ please see styles_device_defaults.xml. <item name="background">@drawable/btn_outlined</item> </style> + <!-- @hide Divider for Autofill half screen dialog --> + <style name="AutofillHalfSheetDivider"> + <item name="android:background">@drawable/autofill_half_sheet_divider</item> + </style> + <!-- @hide Autofill background for popup window (not for fullscreen) --> <style name="AutofillDatasetPicker"> <item name="elevation">4dp</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ef12d8f4ac0f..33ea02ac8172 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3630,6 +3630,7 @@ <java-symbol type="string" name="config_defaultAccessibilityService" /> <java-symbol type="string" name="config_defaultAccessibilityNotificationSound" /> <java-symbol type="string" name="accessibility_shortcut_spoken_feedback" /> + <java-symbol type="array" name="config_trustedAccessibilityServices" /> <java-symbol type="string" name="accessibility_select_shortcut_menu_title" /> <java-symbol type="string" name="accessibility_edit_shortcut_menu_button_title" /> @@ -3703,6 +3704,10 @@ <java-symbol type="id" name="autofill_dataset_list"/> <java-symbol type="id" name="autofill_dataset_picker"/> <java-symbol type="id" name="autofill_dataset_title" /> + <java-symbol type="id" name="autofill_sheet_divider"/> + <java-symbol type="id" name="autofill_sheet_scroll_view"/> + <java-symbol type="id" name="autofill_sheet_scroll_view_space"/> + <java-symbol type="id" name="autofill_save_custom_subtitle" /> <java-symbol type="id" name="autofill_save_icon" /> <java-symbol type="id" name="autofill_save_no" /> @@ -5312,4 +5317,7 @@ <!-- Android MediaRouter framework configs. --> <java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" /> + + <!-- Shutdown thread config flags --> + <java-symbol type="bool" name="config_showPercentageTextDuringRebootToUpdate" /> </resources> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 83d555cbdecd..14a46778810c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -17,6 +17,7 @@ package androidx.window.extensions.embedding; import static android.app.ActivityManager.START_SUCCESS; +import static android.app.ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; @@ -110,6 +111,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen static final boolean ENABLE_SHELL_TRANSITIONS = SystemProperties.getBoolean("persist.wm.debug.shell_transit", true); + // TODO(b/295993745): remove after prebuilt library is updated. + private static final String KEY_ACTIVITY_STACK_TOKEN = + "androidx.window.extensions.embedding.ActivityStackToken"; + @VisibleForTesting @GuardedBy("mLock") final SplitPresenter mPresenter; @@ -2779,8 +2784,17 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TODO(b/232042367): Consolidate the activity create handling so that we can handle // cross-process the same as normal. + IBinder activityStackToken = options.getBinder(KEY_ACTIVITY_STACK_TOKEN); + if (activityStackToken != null) { + // Put activityStack token to #KEY_LAUNCH_TASK_FRAGMENT_TOKEN to launch the activity + // into the taskFragment associated with the token. + options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, activityStackToken); + } + // Early return if the launching taskfragment is already been set. - if (options.getBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) { + // TODO(b/295993745): Use KEY_LAUNCH_TASK_FRAGMENT_TOKEN after WM Jetpack migrates to + // bundle. This is still needed to support #setLaunchingActivityStack. + if (options.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN) != null) { synchronized (mLock) { mCurrentIntent = intent; } @@ -2837,7 +2851,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Amend the request to let the WM know that the activity should be placed in // the dedicated container. // TODO(b/229680885): skip override launching TaskFragment token by split-rule - options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, + options.putBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN, launchedInTaskFragment.getTaskFragmentToken()); mCurrentIntent = intent; } else { @@ -2855,8 +2869,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (mCurrentIntent != null && result != START_SUCCESS) { // Clear the pending appeared intent if the activity was not started // successfully. - final IBinder token = bOptions.getBinder( - ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN); + final IBinder token = bOptions.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN); if (token != null) { final TaskFragmentContainer container = getContainer(token); if (container != null) { diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 0b42c88aa448..f526a280b113 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -230,7 +230,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer. */ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) { - if (mDamageGenerationId == info.damageGenerationId) { + if (mDamageGenerationId == info.damageGenerationId && mDamageGenerationId != 0) { // We hit the same node a second time in the same tree. We don't know the minimal // damage rect anymore, so just push the biggest we can onto our parent's transform // We push directly onto parent in case we are clipped to bounds but have moved position. diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 1f3834be5bef..c9045427bd42 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -262,7 +262,7 @@ private: DisplayList mDisplayList; DisplayList mStagingDisplayList; - int64_t mDamageGenerationId; + int64_t mDamageGenerationId = 0; friend class AnimatorManager; AnimatorManager mAnimatorManager; diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 687feef6c58a..691aa7784d7a 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -196,8 +196,8 @@ public final class MediaRouter2 { * Manifest.permission#MEDIA_CONTENT_CONTROL MEDIA_CONTENT_CONTROL} permission. * @hide */ - // TODO (b/311711420): Deprecate once #getInstance(Context, Looper, String, UserHandle) - // reaches public SDK. + // TODO (b/311711420): Deprecate once #getInstance(Context, String, UserHandle) reaches public + // SDK. @SystemApi @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) @Nullable @@ -206,7 +206,7 @@ public final class MediaRouter2 { // Capturing the IAE here to not break nullability. try { return findOrCreateProxyInstanceForCallingUser( - context, Looper.getMainLooper(), clientPackageName, context.getUser()); + context, clientPackageName, context.getUser()); } catch (IllegalArgumentException ex) { Log.e(TAG, "Package " + clientPackageName + " not found. Ignoring."); return null; @@ -217,8 +217,6 @@ public final class MediaRouter2 { * Returns a proxy MediaRouter2 instance that allows you to control the routing of an app * specified by {@code clientPackageName} and {@code user}. * - * <p>You can specify any {@link Looper} of choice on which internal state updates will run. - * * <p>Proxy MediaRouter2 instances operate differently than regular MediaRouter2 instances: * * <ul> @@ -237,7 +235,6 @@ public final class MediaRouter2 { * </ul> * * @param context The {@link Context} of the caller. - * @param looper The {@link Looper} on which to process internal state changes. * @param clientPackageName The package name of the app you want to control the routing of. * @param user The {@link UserHandle} of the user running the app for which to get the proxy * router instance. Must match {@link Process#myUserHandle()} if the caller doesn't hold @@ -255,10 +252,9 @@ public final class MediaRouter2 { @NonNull public static MediaRouter2 getInstance( @NonNull Context context, - @NonNull Looper looper, @NonNull String clientPackageName, @NonNull UserHandle user) { - return findOrCreateProxyInstanceForCallingUser(context, looper, clientPackageName, user); + return findOrCreateProxyInstanceForCallingUser(context, clientPackageName, user); } /** @@ -270,9 +266,8 @@ public final class MediaRouter2 { */ @NonNull private static MediaRouter2 findOrCreateProxyInstanceForCallingUser( - Context context, Looper looper, String clientPackageName, UserHandle user) { + Context context, String clientPackageName, UserHandle user) { Objects.requireNonNull(context, "context must not be null"); - Objects.requireNonNull(looper, "looper must not be null"); Objects.requireNonNull(user, "user must not be null"); if (TextUtils.isEmpty(clientPackageName)) { @@ -284,7 +279,8 @@ public final class MediaRouter2 { synchronized (sSystemRouterLock) { MediaRouter2 instance = sAppToProxyRouterMap.get(key); if (instance == null) { - instance = new MediaRouter2(context, looper, clientPackageName, user); + instance = + new MediaRouter2(context, Looper.getMainLooper(), clientPackageName, user); // Register proxy router after instantiation to avoid race condition. ((ProxyMediaRouter2Impl) instance.mImpl).registerProxyRouter(); sAppToProxyRouterMap.put(key, instance); diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index abe4a3d18b38..c5729444507e 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -228,11 +228,6 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano } int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) { - if (actualDurationNanos <= 0) { - ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0); return reportActualWorkDurationInternal(&workDuration); @@ -320,23 +315,6 @@ int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) { int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) { WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration); - if (workDuration->workPeriodStartTimestampNanos <= 0) { - ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualTotalDurationNanos <= 0) { - ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualCpuDurationNanos <= 0) { - ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__); - return EINVAL; - } - if (workDuration->actualGpuDurationNanos < 0) { - ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__); - return EINVAL; - } - return reportActualWorkDurationInternal(workDuration); } @@ -428,62 +406,87 @@ APerformanceHintManager* APerformanceHint_getManager() { return APerformanceHintManager::getInstance(); } +#define VALIDATE_PTR(ptr) \ + LOG_ALWAYS_FATAL_IF(ptr == nullptr, "%s: " #ptr " is nullptr", __FUNCTION__); + +#define VALIDATE_INT(value, cmp) \ + if (!(value cmp)) { \ + ALOGE("%s: Invalid value. Check failed: (" #value " " #cmp ") with value: %" PRIi64, \ + __FUNCTION__, value); \ + return EINVAL; \ + } + +#define WARN_INT(value, cmp) \ + if (!(value cmp)) { \ + ALOGE("%s: Invalid value. Check failed: (" #value " " #cmp ") with value: %" PRIi64, \ + __FUNCTION__, value); \ + } + APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager, const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) { + VALIDATE_PTR(manager) + VALIDATE_PTR(threadIds) return manager->createSession(threadIds, size, initialTargetWorkDurationNanos); } int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) { + VALIDATE_PTR(manager) return manager->getPreferredRateNanos(); } int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session, int64_t targetDurationNanos) { + VALIDATE_PTR(session) return session->updateTargetWorkDuration(targetDurationNanos); } int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session, int64_t actualDurationNanos) { + VALIDATE_PTR(session) + VALIDATE_INT(actualDurationNanos, > 0) return session->reportActualWorkDuration(actualDurationNanos); } void APerformanceHint_closeSession(APerformanceHintSession* session) { + VALIDATE_PTR(session) delete session; } int APerformanceHint_sendHint(void* session, SessionHint hint) { + VALIDATE_PTR(session) return reinterpret_cast<APerformanceHintSession*>(session)->sendHint(hint); } int APerformanceHint_setThreads(APerformanceHintSession* session, const pid_t* threadIds, size_t size) { - if (session == nullptr) { - return EINVAL; - } + VALIDATE_PTR(session) + VALIDATE_PTR(threadIds) return session->setThreads(threadIds, size); } int APerformanceHint_getThreadIds(void* aPerformanceHintSession, int32_t* const threadIds, size_t* const size) { - if (aPerformanceHintSession == nullptr) { - return EINVAL; - } + VALIDATE_PTR(aPerformanceHintSession) return static_cast<APerformanceHintSession*>(aPerformanceHintSession) ->getThreadIds(threadIds, size); } int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, bool enabled) { + VALIDATE_PTR(session) return session->setPreferPowerEfficiency(enabled); } int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session, - AWorkDuration* workDuration) { - if (session == nullptr || workDuration == nullptr) { - ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration); - return EINVAL; - } - return session->reportActualWorkDuration(workDuration); + AWorkDuration* workDurationPtr) { + VALIDATE_PTR(session) + VALIDATE_PTR(workDurationPtr) + WorkDuration& workDuration = *static_cast<WorkDuration*>(workDurationPtr); + VALIDATE_INT(workDuration.workPeriodStartTimestampNanos, > 0) + VALIDATE_INT(workDuration.actualTotalDurationNanos, > 0) + VALIDATE_INT(workDuration.actualCpuDurationNanos, > 0) + VALIDATE_INT(workDuration.actualGpuDurationNanos, >= 0) + return session->reportActualWorkDuration(workDurationPtr); } AWorkDuration* AWorkDuration_create() { @@ -492,46 +495,36 @@ AWorkDuration* AWorkDuration_create() { } void AWorkDuration_release(AWorkDuration* aWorkDuration) { - if (aWorkDuration == nullptr) { - ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__); - } + VALIDATE_PTR(aWorkDuration) delete aWorkDuration; } void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration, int64_t workPeriodStartTimestampNanos) { - if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos); - } + VALIDATE_PTR(aWorkDuration) + WARN_INT(workPeriodStartTimestampNanos, > 0) static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos = workPeriodStartTimestampNanos; } void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration, int64_t actualTotalDurationNanos) { - if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualTotalDurationNanos); - } + VALIDATE_PTR(aWorkDuration) + WARN_INT(actualTotalDurationNanos, > 0) static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos; } void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration, int64_t actualCpuDurationNanos) { - if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualCpuDurationNanos); - } + VALIDATE_PTR(aWorkDuration) + WARN_INT(actualCpuDurationNanos, > 0) static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos; } void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration, int64_t actualGpuDurationNanos) { - if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) { - ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")", - __FUNCTION__, aWorkDuration, actualGpuDurationNanos); - } + VALIDATE_PTR(aWorkDuration) + WARN_INT(actualGpuDurationNanos, >= 0) static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos; } diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index dc2a625aacfd..3524f8cce04c 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -45,7 +45,7 @@ package android.nfc { package android.nfc.cardemulation { public final class CardEmulation { - method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public android.nfc.cardemulation.ApduServiceInfo getPreferredPaymentService(); + method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int); } diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index 0943392a68ad..9d38e4c5b297 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -16,6 +16,7 @@ package android.nfc.cardemulation; +import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -23,6 +24,7 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; +import android.annotation.UserHandleAware; import android.annotation.UserIdInt; import android.app.Activity; import android.content.ComponentName; @@ -1138,31 +1140,28 @@ public final class CardEmulation { } /** - * Returns the {@link Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT} for the given user. + * Returns the value of {@link Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT}. + * + * @param context A context + * @return A ComponentName for the setting value, or null. * * @hide */ @SystemApi + @UserHandleAware @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) + @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck") @FlaggedApi(android.permission.flags.Flags.FLAG_WALLET_ROLE_ENABLED) @Nullable - public ApduServiceInfo getPreferredPaymentService() { - try { - return sService.getPreferredPaymentService(mContext.getUser().getIdentifier()); - } catch (RemoteException e) { - // Try one more time - recoverService(); - if (sService == null) { - Log.e(TAG, "Failed to recover CardEmulationService."); - return null; - } - try { - return sService.getPreferredPaymentService(mContext.getUser().getIdentifier()); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to reach CardEmulationService."); - return null; - } + public static ComponentName getPreferredPaymentService(@NonNull Context context) { + context.checkCallingOrSelfPermission(Manifest.permission.NFC_PREFERRED_PAYMENT_INFO); + String defaultPaymentComponent = Settings.Secure.getString(context.getContentResolver(), + Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT); + + if (defaultPaymentComponent == null) { + return null; } - } + return ComponentName.unflattenFromString(defaultPaymentComponent); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java index 5d520ce5d81f..7e2d0af5c075 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java @@ -21,6 +21,8 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import androidx.annotation.NonNull; + /** * A class for applying config changes and determing if doing so resulting in any "interesting" * changes. @@ -48,8 +50,15 @@ public class InterestingConfigChanges { */ @SuppressLint("NewApi") public boolean applyNewConfig(Resources res) { + return applyNewConfig(res.getConfiguration()); + } + + /** + * Applies the given config change and returns whether an "interesting" change happened. + */ + public boolean applyNewConfig(@NonNull Configuration configuration) { int configChanges = mLastConfiguration.updateFrom( - Configuration.generateDelta(mLastConfiguration, res.getConfiguration())); + Configuration.generateDelta(mLastConfiguration, configuration)); return (configChanges & (mFlags)) != 0; } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index d61ae7eccc42..80656e9253db 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -361,6 +361,7 @@ android_library { "androidx.test.ext.junit", "androidx.test.ext.truth", "kotlin-test", + "SystemUICustomizationTestUtils", ], libs: [ "android.test.runner", @@ -439,6 +440,7 @@ android_robolectric_test { "androidx.test.ext.junit", "inline-mockito-robolectric-prebuilt", "platform-parametric-runner-lib", + "SystemUICustomizationTestUtils", ], libs: [ "android.test.runner", diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 2052e2c01410..a7557d8880e3 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -17,6 +17,7 @@ package com.android.systemui.compose +import android.app.Dialog import android.content.Context import android.view.View import android.view.WindowInsets @@ -26,11 +27,13 @@ import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.widgets.WidgetConfigurator +import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import com.android.systemui.statusbar.phone.SystemUIDialogFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @@ -78,6 +81,13 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun createStickyKeysDialog( + dialogFactory: SystemUIDialogFactory, + viewModel: StickyKeysIndicatorViewModel + ): Dialog { + throwComposeUnavailableError() + } + override fun createCommunalView( context: Context, viewModel: BaseCommunalViewModel, diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index b607d596390d..d63939d3b699 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -16,6 +16,7 @@ package com.android.systemui.compose +import android.app.Dialog import android.content.Context import android.graphics.Point import android.view.View @@ -38,6 +39,8 @@ import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.compose.CommunalHub import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.widgets.WidgetConfigurator +import com.android.systemui.keyboard.stickykeys.ui.view.StickyKeysIndicator +import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.compose.FooterActions @@ -47,6 +50,8 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import com.android.systemui.statusbar.phone.create import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -120,6 +125,13 @@ object ComposeFacade : BaseComposeFacade { } } + override fun createStickyKeysDialog( + dialogFactory: SystemUIDialogFactory, + viewModel: StickyKeysIndicatorViewModel + ): Dialog { + return dialogFactory.create { StickyKeysIndicator(viewModel) } + } + override fun createCommunalView( context: Context, viewModel: BaseCommunalViewModel, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt new file mode 100644 index 000000000000..68e57b5d51b8 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.ui.view + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel + +@Composable +fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) { + val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap()) + StickyKeysIndicator(stickyKeys) +} + +@Composable +fun StickyKeysIndicator(stickyKeys: Map<ModifierKey, Locked>, modifier: Modifier = Modifier) { + Surface( + color = MaterialTheme.colorScheme.surface, + shape = MaterialTheme.shapes.medium, + modifier = modifier + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(16.dp) + ) { + stickyKeys.forEach { (key, isLocked) -> + key(key) { + Text( + text = key.text, + fontWeight = if (isLocked.locked) FontWeight.Bold else FontWeight.Normal + ) + } + } + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index 9778e53d8f69..c027c499c0b7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -16,17 +16,16 @@ package com.android.systemui.qs.ui.composable -import android.view.ContextThemeWrapper import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp @@ -53,14 +52,6 @@ object QuickSettings { } } -@Composable -private fun QuickSettingsTheme(content: @Composable () -> Unit) { - val context = LocalContext.current - val themedContext = - remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) } - CompositionLocalProvider(LocalContext provides themedContext) { content() } -} - private fun SceneScope.stateForQuickSettingsContent(): QSSceneAdapter.State { return when (val transitionState = layoutState.transitionState) { is TransitionState.Idle -> { @@ -115,6 +106,7 @@ private fun QuickSettingsContent( modifier: Modifier = Modifier, ) { val qsView by qsSceneAdapter.qsView.collectAsState(null) + val isCustomizing by qsSceneAdapter.isCustomizing.collectAsState() QuickSettingsTheme { val context = LocalContext.current @@ -124,14 +116,27 @@ private fun QuickSettingsContent( } } qsView?.let { view -> - AndroidView( - modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)), - factory = { _ -> - qsSceneAdapter.setState(state) - view - }, - update = { qsSceneAdapter.setState(state) } - ) + Box( + modifier = + modifier + .fillMaxWidth() + .then( + if (isCustomizing) { + Modifier.fillMaxHeight() + } else { + Modifier.wrapContentHeight() + } + ) + ) { + AndroidView( + modifier = Modifier.fillMaxWidth().background(colorAttr(R.attr.underSurface)), + factory = { _ -> + qsSceneAdapter.setState(state) + view + }, + update = { qsSceneAdapter.setState(state) } + ) + } } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index d8c7290b76b8..bbfe0fda049a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -24,31 +24,44 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background +import androidx.compose.foundation.clipScrollableContainer +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.TransitionState import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace +import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.scene.ui.composable.toTransitionSceneKey import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader import com.android.systemui.shade.ui.composable.Shade @@ -105,57 +118,120 @@ private fun SceneScope.QuickSettingsScene( ) { // TODO(b/280887232): implement the real UI. Box(modifier = modifier.fillMaxSize()) { - Box(modifier = Modifier.fillMaxSize()) { - val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() - val collapsedHeaderHeight = - with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } - Spacer( - modifier = - Modifier.element(Shade.Elements.ScrimBackground) - .fillMaxSize() - .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim) - ) - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, bottom = 48.dp) - ) { - when (LocalWindowSizeClass.current.widthSizeClass) { - WindowWidthSizeClass.Compact -> - AnimatedVisibility( - visible = !isCustomizing, - enter = - expandVertically( - animationSpec = tween(1000), - initialHeight = { collapsedHeaderHeight }, - ) + fadeIn(tween(1000)), - exit = - shrinkVertically( - animationSpec = tween(1000), - targetHeight = { collapsedHeaderHeight }, - shrinkTowards = Alignment.Top, - ) + fadeOut(tween(1000)), - ) { - ExpandedShadeHeader( + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() + val collapsedHeaderHeight = + with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } + val lifecycleOwner = LocalLifecycleOwner.current + val footerActionsViewModel = + remember(lifecycleOwner, viewModel) { + viewModel.getFooterActionsViewModel(lifecycleOwner) + } + val scrollState = rememberScrollState() + // When animating into the scene, we don't want it to be able to scroll, as it could mess + // up with the expansion animation. + val isScrollable = + when (val state = layoutState.transitionState) { + is TransitionState.Idle -> true + is TransitionState.Transition -> { + state.fromScene == SceneKey.QuickSettings.toTransitionSceneKey() + } + } + + LaunchedEffect(isCustomizing, scrollState) { + if (isCustomizing) { + scrollState.scrollTo(0) + } + } + + // This is the background for the whole scene, as the elements don't necessarily provide + // a background that extends to the edges. + Spacer( + modifier = + Modifier.element(Shade.Elements.ScrimBackground) + .fillMaxSize() + .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim) + ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier.fillMaxSize() + // bottom should be tied to insets + .padding(bottom = 16.dp) + ) { + Box(modifier = Modifier.fillMaxSize().weight(1f)) { + val shadeHeaderAndQuickSettingsModifier = + if (isCustomizing) { + Modifier.fillMaxHeight().align(Alignment.TopCenter) + } else { + Modifier.verticalNestedScrollToScene() + .verticalScroll( + scrollState, + enabled = isScrollable, + ) + .clipScrollableContainer(Orientation.Horizontal) + .fillMaxWidth() + .wrapContentHeight(unbounded = true) + .align(Alignment.TopCenter) + } + + Column( + modifier = shadeHeaderAndQuickSettingsModifier, + ) { + when (LocalWindowSizeClass.current.widthSizeClass) { + WindowWidthSizeClass.Compact -> + AnimatedVisibility( + visible = !isCustomizing, + enter = + expandVertically( + animationSpec = tween(100), + initialHeight = { collapsedHeaderHeight }, + ) + fadeIn(tween(100)), + exit = + shrinkVertically( + animationSpec = tween(100), + targetHeight = { collapsedHeaderHeight }, + shrinkTowards = Alignment.Top, + ) + fadeOut(tween(100)), + ) { + ExpandedShadeHeader( + viewModel = viewModel.shadeHeaderViewModel, + createTintedIconManager = createTintedIconManager, + createBatteryMeterViewController = + createBatteryMeterViewController, + statusBarIconController = statusBarIconController, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + else -> + CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, + modifier = Modifier.padding(horizontal = 16.dp), ) - } - else -> - CollapsedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, - ) + } + Spacer(modifier = Modifier.height(16.dp)) + // This view has its own horizontal padding + QuickSettings( + modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"), + viewModel.qsSceneAdapter, + ) + } + } + AnimatedVisibility( + visible = !isCustomizing, + modifier = Modifier.align(Alignment.CenterHorizontally).fillMaxWidth() + ) { + QuickSettingsTheme { + // This view has its own horizontal padding + // TODO(b/321716470) This should use a lifecycle tied to the scene. + FooterActions( + viewModel = footerActionsViewModel, + qsVisibilityLifecycleOwner = lifecycleOwner, + modifier = Modifier.element(QuickSettings.Elements.FooterActions) + ) } - Spacer(modifier = Modifier.height(16.dp)) - QuickSettings( - modifier = Modifier.fillMaxHeight(), - viewModel.qsSceneAdapter, - ) } } HeadsUpNotificationSpace( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt new file mode 100644 index 000000000000..87b6f95b0ae6 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsTheme.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.ui.composable + +import android.view.ContextThemeWrapper +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.android.systemui.res.R + +@Composable +fun QuickSettingsTheme(content: @Composable () -> Unit) { + val context = LocalContext.current + val themedContext = + remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) } + CompositionLocalProvider(LocalContext provides themedContext) { content() } +} diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp index 1d1849680040..81b5bd43bdbd 100644 --- a/packages/SystemUI/customization/Android.bp +++ b/packages/SystemUI/customization/Android.bp @@ -34,15 +34,19 @@ android_library { "PluginCoreLib", "SystemUIPluginLib", "SystemUIUnfoldLib", - "androidx.dynamicanimation_dynamicanimation", + "kotlinx_coroutines", + "dagger2", + "jsr330", + ], + libs: [ + // Keep android-specific libraries as libs instead of static_libs, so that they don't break + // things when included as transitive dependencies in robolectric targets. "androidx.concurrent_concurrent-futures", + "androidx.dynamicanimation_dynamicanimation", "androidx.lifecycle_lifecycle-runtime-ktx", "androidx.lifecycle_lifecycle-viewmodel-ktx", "androidx.recyclerview_recyclerview", "kotlinx_coroutines_android", - "kotlinx_coroutines", - "dagger2", - "jsr330", ], resource_dirs: [ "res", diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt new file mode 100644 index 000000000000..ea766f8ea9bb --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.haptics.slider + +import android.widget.SeekBar +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.fakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class SeekableSliderHapticPluginTest : SysuiTestCase() { + + private val kosmos = Kosmos() + + @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var vibratorHelper: VibratorHelper + private val seekBar = SeekBar(mContext) + private lateinit var plugin: SeekableSliderHapticPlugin + + @Before + fun setup() { + whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0)) + } + + @Test + fun start_beginsTrackingSlider() = runOnStartedPlugin { assertThat(plugin.isTracking).isTrue() } + + @Test + fun stop_stopsTrackingSlider() = runOnStartedPlugin { + // WHEN called to stop + plugin.stop() + + // THEN stops tracking + assertThat(plugin.isTracking).isFalse() + } + + @Test + fun start_afterStop_startsTheTrackingAgain() = runOnStartedPlugin { + // WHEN the plugin is restarted + plugin.stop() + plugin.start() + + // THEN the tracking begins again + assertThat(plugin.isTracking).isTrue() + } + + @Test + fun onKeyDown_startsWaiting() = runOnStartedPlugin { + // WHEN a keyDown event is recorded + plugin.onKeyDown() + + // THEN the timer starts waiting + assertThat(plugin.isKeyUpTimerWaiting).isTrue() + } + + @Test + fun keyUpWaitComplete_triggersOnArrowUp() = runOnStartedPlugin { + // GIVEN an onKeyDown that starts the wait and a program progress change that advances the + // slider state to ARROW_HANDLE_MOVED_ONCE + plugin.onKeyDown() + plugin.onProgressChanged(seekBar, 50, false) + testScheduler.runCurrent() + assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE) + + // WHEN the key-up wait completes after the timeout plus a small buffer + advanceTimeBy(KEY_UP_TIMEOUT + 10L) + + // THEN the onArrowUp event is delivered causing the slider tracker to move to IDLE + assertThat(plugin.trackerState).isEqualTo(SliderState.IDLE) + assertThat(plugin.isKeyUpTimerWaiting).isFalse() + } + + @Test + fun onKeyDown_whileWaiting_restartsWait() = runOnStartedPlugin { + // GIVEN an onKeyDown that starts the wait and a program progress change that advances the + // slider state to ARROW_HANDLE_MOVED_ONCE + plugin.onKeyDown() + plugin.onProgressChanged(seekBar, 50, false) + testScheduler.runCurrent() + assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE) + + // WHEN half the timeout period has elapsed and a new keyDown event occurs + advanceTimeBy(KEY_UP_TIMEOUT / 2) + plugin.onKeyDown() + + // AFTER advancing by a period of time that should have complete the original wait + advanceTimeBy(KEY_UP_TIMEOUT / 2 + 10L) + + // THEN the timer is still waiting and the slider tracker remains on ARROW_HANDLE_MOVED_ONCE + assertThat(plugin.isKeyUpTimerWaiting).isTrue() + assertThat(plugin.trackerState).isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE) + } + + private fun runOnStartedPlugin(test: suspend TestScope.() -> Unit) = + with(kosmos) { + testScope.runTest { + createPlugin(this, UnconfinedTestDispatcher(testScheduler)) + // GIVEN that the plugin is started + plugin.start() + + // THEN run the test + test() + } + } + + private fun createPlugin(scope: CoroutineScope, dispatcher: CoroutineDispatcher) { + plugin = + SeekableSliderHapticPlugin( + vibratorHelper, + kosmos.fakeSystemClock, + dispatcher, + scope, + ) + } + + companion object { + private const val KEY_UP_TIMEOUT = 100L + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt index d9b1ea1aedcc..cae20d006dec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -16,12 +16,16 @@ package com.android.systemui.qs.ui.adapter +import android.content.res.Configuration import android.os.Bundle +import android.view.Surface import android.view.View import androidx.asynclayoutinflater.view.AsyncLayoutInflater import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.qs.QSImpl import com.android.systemui.qs.dagger.QSComponent @@ -34,6 +38,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import java.util.Locale import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -81,11 +86,17 @@ class QSSceneAdapterImplTest : SysuiTestCase() { .also { components.add(it) } } } + private val configuration = Configuration(context.resources.configuration) + + private val fakeConfigurationRepository = + FakeConfigurationRepository().apply { onConfigurationChange(configuration) } + private val configurationInteractor = ConfigurationInteractor(fakeConfigurationRepository) private val mockAsyncLayoutInflater = mock<AsyncLayoutInflater>() { whenever(inflate(anyInt(), nullable(), any())).then { invocation -> val mockView = mock<View>() + whenever(mockView.context).thenReturn(context) invocation .getArgument<AsyncLayoutInflater.OnInflateFinishedListener>(2) .onInflateFinished( @@ -102,6 +113,7 @@ class QSSceneAdapterImplTest : SysuiTestCase() { qsImplProvider, testDispatcher, testScope.backgroundScope, + configurationInteractor, { mockAsyncLayoutInflater }, ) @@ -297,6 +309,9 @@ class QSSceneAdapterImplTest : SysuiTestCase() { @Test fun reinflation_previousStateDestroyed() = testScope.runTest { + // Run all flows... In particular, initial configuration propagation that could cause + // QSImpl to re-inflate. + runCurrent() val qsImpl by collectLastValue(underTest.qsImpl) underTest.inflate(context) @@ -322,4 +337,81 @@ class QSSceneAdapterImplTest : SysuiTestCase() { bundleArgCaptor.value, ) } + + @Test + fun changeInLocale_reinflation() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + + val oldQsImpl = qsImpl!! + + val newLocale = + if (configuration.locales[0] == Locale("en-US")) { + Locale("es-UY") + } else { + Locale("en-US") + } + configuration.setLocale(newLocale) + fakeConfigurationRepository.onConfigurationChange(configuration) + runCurrent() + + assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!) + } + + @Test + fun changeInFontSize_reinflation() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + + val oldQsImpl = qsImpl!! + + configuration.fontScale *= 2 + fakeConfigurationRepository.onConfigurationChange(configuration) + runCurrent() + + assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!) + } + + @Test + fun changeInAssetPath_reinflation() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + + val oldQsImpl = qsImpl!! + + configuration.assetsSeq += 1 + fakeConfigurationRepository.onConfigurationChange(configuration) + runCurrent() + + assertThat(oldQsImpl).isNotSameInstanceAs(qsImpl!!) + } + + @Test + fun otherChangesInConfiguration_noReinflation_configurationChangeDispatched() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + + val oldQsImpl = qsImpl!! + configuration.densityDpi *= 2 + configuration.windowConfiguration.maxBounds.scale(2f) + configuration.windowConfiguration.rotation = Surface.ROTATION_270 + fakeConfigurationRepository.onConfigurationChange(configuration) + runCurrent() + + assertThat(oldQsImpl).isSameInstanceAs(qsImpl!!) + verify(qsImpl!!).onConfigurationChanged(configuration) + verify(qsImpl!!.view).dispatchConfigurationChanged(configuration) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index d7a794149869..42200a3d33ec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -23,6 +23,8 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.FooterActionsController +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Direction @@ -39,12 +41,16 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsVi import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.times +import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) @@ -56,6 +62,12 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() } + private val footerActionsViewModel = mock<FooterActionsViewModel>() + private val footerActionsViewModelFactory = + mock<FooterActionsViewModel.Factory> { + whenever(create(any())).thenReturn(footerActionsViewModel) + } + private val footerActionsController = mock<FooterActionsController>() private var mobileIconsViewModel: MobileIconsViewModel = MobileIconsViewModel( @@ -94,6 +106,8 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, notifications = kosmos.notificationsPlaceholderViewModel, + footerActionsViewModelFactory, + footerActionsController, ) } @@ -125,4 +139,12 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { ) ) } + + @Test + fun gettingViewModelInitializesControllerOnlyOnce() { + underTest.getFooterActionsViewModel(mock()) + underTest.getFooterActionsViewModel(mock()) + + verify(footerActionsController, times(1)).init() + } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java index 3d9645a3d983..b1736b16875d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java @@ -227,5 +227,10 @@ public interface VolumeDialogController { void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch); // requires version 2 void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs); + + /** + * Callback function for when the volume changed due to a physical key press. + */ + void onVolumeChangedFromKey(); } } diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index e8201ecba623..bc221b8e4e01 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1936,5 +1936,9 @@ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen> <!-- UDFPS view attributes --> - <dimen name="udfps_icon_size">6mm</dimen> + <!-- UDFPS icon size in microns/um --> + <dimen name="udfps_icon_size" format="float">6000</dimen> + <!-- Microns/ums (1000 um = 1mm) per pixel for the given device. If unspecified, UI that + relies on this value will not be sized correctly. --> + <item name="pixel_pitch" format="float" type="dimen">-1</item> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index 4fc1b5841047..a77cc1fea6a6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.domain.interactor import android.content.Context +import android.util.Log import android.view.MotionEvent import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams @@ -42,12 +43,23 @@ import kotlinx.coroutines.flow.stateIn class UdfpsOverlayInteractor @Inject constructor( - @Application context: Context, + @Application private val context: Context, private val authController: AuthController, private val selectedUserInteractor: SelectedUserInteractor, @Application scope: CoroutineScope ) { - private val iconSize: Int = context.resources.getDimensionPixelSize(R.dimen.udfps_icon_size) + private fun calculateIconSize(): Int { + val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch) + if (pixelPitch <= 0) { + Log.e( + "UdfpsOverlayInteractor", + "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device." + ) + } + return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt() + } + + private var iconSize: Int = calculateIconSize() /** Whether a touch is within the under-display fingerprint sensor area */ fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt index 13539850a598..5f6ff82c6038 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt @@ -72,6 +72,9 @@ class ConfigurationInteractor @Inject constructor(private val repository: Config val onAnyConfigurationChange: Flow<Unit> = repository.onAnyConfigurationChange.onStart { emit(Unit) } + /** Emits the new configuration on any configuration change */ + val configurationValues: Flow<Configuration> = repository.configurationValues + /** Emits the current resolution scaling factor */ val scaleForResolution: Flow<Float> = repository.scaleForResolution } diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 641064becf24..2f9fd2e99619 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -17,6 +17,7 @@ package com.android.systemui.compose +import android.app.Dialog import android.content.Context import android.view.View import android.view.WindowInsets @@ -26,11 +27,13 @@ import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.widgets.WidgetConfigurator +import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import com.android.systemui.statusbar.phone.SystemUIDialogFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @@ -86,6 +89,12 @@ interface BaseComposeFacade { sceneByKey: Map<SceneKey, Scene>, ): View + /** Creates sticky key dialog presenting provided [viewModel] **/ + fun createStickyKeysDialog( + dialogFactory: SystemUIDialogFactory, + viewModel: StickyKeysIndicatorViewModel + ): Dialog + /** Create a [View] to represent [viewModel] on screen. */ fun createCommunalView( context: Context, diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java index db0c3c68107a..0fd688760a32 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java +++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java @@ -18,10 +18,7 @@ package com.android.systemui.doze.dagger; import android.content.Context; import android.hardware.Sensor; -import android.os.Handler; -import com.android.systemui.res.R; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.doze.DozeAuthRemover; import com.android.systemui.doze.DozeBrightnessHostForwarder; @@ -40,6 +37,7 @@ import com.android.systemui.doze.DozeTransitionListener; import com.android.systemui.doze.DozeTriggers; import com.android.systemui.doze.DozeUi; import com.android.systemui.doze.DozeWallpaperState; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.sensors.AsyncSensorManager; @@ -75,9 +73,8 @@ public abstract class DozeModule { @Provides @DozeScope - static WakeLock providesDozeWakeLock(DelayedWakeLock.Builder delayedWakeLockBuilder, - @Main Handler handler) { - return delayedWakeLockBuilder.setHandler(handler).setTag("Doze").build(); + static WakeLock providesDozeWakeLock(DelayedWakeLock.Factory delayedWakeLockFactory) { + return delayedWakeLockFactory.create("Doze"); } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 846736c04d98..3f7c152f47d8 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -574,9 +574,6 @@ object Flags { @JvmField val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog") - // TODO(b/289573946): Tracking Bug - @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text") - // TODO(b/302087895): Tracking Bug @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data", teamfood = true) diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt new file mode 100644 index 000000000000..58fb6a95b872 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.haptics.slider + +import android.view.MotionEvent +import android.view.VelocityTracker +import android.widget.SeekBar +import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +/** + * A plugin added to a manager of a [android.widget.SeekBar] that adds dynamic haptic feedback. + * + * A [SeekableSliderEventProducer] is used as the producer of slider events, a + * [SliderHapticFeedbackProvider] is used as the listener of slider states to play haptic feedback + * depending on the state, and a [SeekableSliderTracker] is used as the state machine handler that + * tracks and manipulates the slider state. + */ +class SeekableSliderHapticPlugin +@JvmOverloads +constructor( + vibratorHelper: VibratorHelper, + systemClock: SystemClock, + @Main private val mainDispatcher: CoroutineDispatcher, + @Application private val applicationScope: CoroutineScope, + sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(), + sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(), +) { + + private val velocityTracker = VelocityTracker.obtain() + + private val sliderEventProducer = SeekableSliderEventProducer() + + private val sliderHapticFeedbackProvider = + SliderHapticFeedbackProvider( + vibratorHelper, + velocityTracker, + sliderHapticFeedbackConfig, + systemClock, + ) + + private val sliderTracker = + SeekableSliderTracker( + sliderHapticFeedbackProvider, + sliderEventProducer, + mainDispatcher, + sliderTrackerConfig, + ) + + val isTracking: Boolean + get() = sliderTracker.isTracking + + val trackerState: SliderState + get() = sliderTracker.currentState + + /** + * A waiting [Job] for a timer that estimates the key-up event when a key-down event is + * received. + * + * This is useful for the cases where the slider is being operated by an external key, but the + * release of the key is not easily accessible (e.g., the volume keys) + */ + private var keyUpJob: Job? = null + + @VisibleForTesting + val isKeyUpTimerWaiting: Boolean + get() = keyUpJob != null && keyUpJob?.isActive == true + + /** + * Start the plugin. + * + * This starts the tracking of slider states, events and triggering of haptic feedback. + */ + fun start() { + if (!isTracking) { + sliderTracker.startTracking() + } + } + + /** + * Stop the plugin + * + * This stops the tracking of slider states, events and triggers of haptic feedback. + */ + fun stop() = sliderTracker.stopTracking() + + /** React to a touch event */ + fun onTouchEvent(event: MotionEvent?) { + when (event?.actionMasked) { + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> velocityTracker.clear() + MotionEvent.ACTION_DOWN, + MotionEvent.ACTION_MOVE -> velocityTracker.addMovement(event) + } + } + + /** onStartTrackingTouch event from the slider's [android.widget.SeekBar] */ + fun onStartTrackingTouch(seekBar: SeekBar) { + if (isTracking) { + sliderEventProducer.onStartTrackingTouch(seekBar) + } + } + + /** onProgressChanged event from the slider's [android.widget.SeekBar] */ + fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (isTracking) { + sliderEventProducer.onProgressChanged(seekBar, progress, fromUser) + } + } + + /** onStopTrackingTouch event from the slider's [android.widget.SeekBar] */ + fun onStopTrackingTouch(seekBar: SeekBar) { + if (isTracking) { + sliderEventProducer.onStopTrackingTouch(seekBar) + } + } + + /** onArrowUp event recorded */ + fun onArrowUp() { + if (isTracking) { + sliderEventProducer.onArrowUp() + } + } + + /** + * An external key was pressed (e.g., a volume key). + * + * This event is used to estimate the key-up event based on by running a timer as a waiting + * coroutine in the [keyUpTimerScope]. A key-up event in a slider corresponds to an onArrowUp + * event. Therefore, [onArrowUp] must be called after the timeout. + */ + fun onKeyDown() { + if (!isTracking) return + + if (isKeyUpTimerWaiting) { + // Cancel the ongoing wait + keyUpJob?.cancel() + } + keyUpJob = + applicationScope.launch { + delay(KEY_UP_TIMEOUT) + onArrowUp() + } + } + + companion object { + const val KEY_UP_TIMEOUT = 100L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt index 37034f63aca7..4ed812010100 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt @@ -26,11 +26,20 @@ import javax.inject.Inject private const val TAG = "stickyKeys" class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) { - fun logNewStickyKeysReceived(linkedHashMap: Map<ModifierKey, Locked>) { + fun logNewStickyKeysReceived(stickyKeys: Map<ModifierKey, Locked>) { buffer.log( TAG, LogLevel.VERBOSE, - { str1 = linkedHashMap.toString() }, + { str1 = stickyKeys.toString() }, + { "new sticky keys state received: $str1" } + ) + } + + fun logNewUiState(stickyKeys: Map<ModifierKey, Locked>) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = stickyKeys.toString() }, { "new sticky keys state received: $str1" } ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt new file mode 100644 index 000000000000..b68551bf93b3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinator.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.ui + +import android.app.Dialog +import android.util.Log +import android.view.Gravity +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.Window +import android.view.WindowManager +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel +import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +@SysUISingleton +class StickyKeysIndicatorCoordinator +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val dialogFactory: SystemUIDialogFactory, + private val viewModel: StickyKeysIndicatorViewModel, + private val stickyKeysLogger: StickyKeysLogger, +) { + + private var dialog: Dialog? = null + + fun startListening() { + // this check needs to be moved to PhysicalKeyboardCoreStartable + if (!ComposeFacade.isComposeAvailable()) { + Log.e("StickyKeysIndicatorCoordinator", "Compose is required for this UI") + return + } + applicationScope.launch { + viewModel.indicatorContent.collect { stickyKeys -> + stickyKeysLogger.logNewUiState(stickyKeys) + if (stickyKeys.isEmpty()) { + dialog?.dismiss() + dialog = null + } else if (dialog == null) { + dialog = ComposeFacade.createStickyKeysDialog(dialogFactory, viewModel).apply { + setCanceledOnTouchOutside(false) + window?.setAttributes() + show() + } + } + } + } + } + + private fun Window.setAttributes() { + setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) + addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + setGravity(Gravity.TOP or Gravity.END) + attributes = WindowManager.LayoutParams().apply { + copyFrom(attributes) + width = WRAP_CONTENT + title = "StickyKeysIndicator" + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index a3b92541d593..a2dfc0159c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -27,8 +27,11 @@ import android.graphics.PointF; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.systemui.Dumpable; import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.res.R; @@ -53,6 +56,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private QuickStatusBarHeader mHeader; private float mQsExpansion; private QSCustomizer mQSCustomizer; + private QSPanel mQSPanel; private NonInterceptingScrollView mQSPanelContainer; private int mHorizontalMargins; @@ -72,6 +76,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { protected void onFinishInflate() { super.onFinishInflate(); mQSPanelContainer = findViewById(R.id.expanded_qs_scroll_view); + mQSPanel = findViewById(R.id.quick_settings_panel); mHeader = findViewById(R.id.header); mQSCustomizer = findViewById(R.id.qs_customize); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); @@ -79,6 +84,13 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { void setSceneContainerEnabled(boolean enabled) { mSceneContainerEnabled = enabled; + if (enabled) { + mQSPanelContainer.removeAllViews(); + removeView(mQSPanelContainer); + LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + addView(mQSPanel, 0, lp); + } } @Override @@ -97,20 +109,26 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // QSPanel will show as many rows as it can (up to TileLayout.MAX_ROWS) such that the // bottom and footer are inside the screen. - MarginLayoutParams layoutParams = (MarginLayoutParams) mQSPanelContainer.getLayoutParams(); - int availableHeight = View.MeasureSpec.getSize(heightMeasureSpec); - int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin - - getPaddingBottom(); - int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin - + layoutParams.rightMargin; - final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding, - layoutParams.width); - mQSPanelContainer.measure(qsPanelWidthSpec, - MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); - int width = mQSPanelContainer.getMeasuredWidth() + padding; - super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY)); + + if (!mSceneContainerEnabled) { + MarginLayoutParams layoutParams = + (MarginLayoutParams) mQSPanelContainer.getLayoutParams(); + int maxQs = availableHeight - layoutParams.topMargin - layoutParams.bottomMargin + - getPaddingBottom(); + int padding = mPaddingLeft + mPaddingRight + layoutParams.leftMargin + + layoutParams.rightMargin; + final int qsPanelWidthSpec = getChildMeasureSpec(widthMeasureSpec, padding, + layoutParams.width); + mQSPanelContainer.measure(qsPanelWidthSpec, + MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST)); + int width = mQSPanelContainer.getMeasuredWidth() + padding; + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY)); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + // QSCustomizer will always be the height of the screen, but do this after // other measuring to avoid changing the height of the QS. mQSCustomizer.measure(widthMeasureSpec, @@ -130,12 +148,15 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { @Override protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { - // Do not measure QSPanel again when doing super.onMeasure. - // This prevents the pages in PagedTileLayout to be remeasured with a different (incorrect) - // size to the one used for determining the number of rows and then the number of pages. - if (child != mQSPanelContainer) { - super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, - parentHeightMeasureSpec, heightUsed); + if (!mSceneContainerEnabled) { + // Do not measure QSPanel again when doing super.onMeasure. + // This prevents the pages in PagedTileLayout to be remeasured with a different + // (incorrect) size to the one used for determining the number of rows and then the + // number of pages. + if (child != mQSPanelContainer) { + super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, + parentHeightMeasureSpec, heightUsed); + } } } @@ -151,6 +172,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { updateClippingPath(); } + @Nullable public NonInterceptingScrollView getQSPanelContainer() { return mQSPanelContainer; } @@ -172,11 +194,19 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { .getDimensionPixelSize( R.dimen.large_screen_shade_header_height); } - mQSPanelContainer.setPaddingRelative( - mQSPanelContainer.getPaddingStart(), - mSceneContainerEnabled ? 0 : topPadding, - mQSPanelContainer.getPaddingEnd(), - mQSPanelContainer.getPaddingBottom()); + if (mQSPanelContainer != null) { + mQSPanelContainer.setPaddingRelative( + mQSPanelContainer.getPaddingStart(), + mSceneContainerEnabled ? 0 : topPadding, + mQSPanelContainer.getPaddingEnd(), + mQSPanelContainer.getPaddingBottom()); + } else { + mQSPanel.setPaddingRelative( + mQSPanel.getPaddingStart(), + mSceneContainerEnabled ? 0 : topPadding, + mQSPanel.getPaddingEnd(), + mQSPanel.getPaddingBottom()); + } int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin); int horizontalPadding = getResources().getDimensionPixelSize( @@ -220,7 +250,9 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { public void setExpansion(float expansion) { mQsExpansion = expansion; - mQSPanelContainer.setScrollingEnabled(expansion > 0f); + if (mQSPanelContainer != null) { + mQSPanelContainer.setScrollingEnabled(expansion > 0f); + } updateExpansion(); } @@ -239,7 +271,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { lp.rightMargin = mHorizontalMargins; lp.leftMargin = mHorizontalMargins; } - if (view == mQSPanelContainer) { + if (view == mQSPanelContainer || view == mQSPanel) { // QS panel lays out some of its content full width qsPanelController.setContentMargins(mContentHorizontalPadding, mContentHorizontalPadding); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java index 7b001c7b72f7..ffbc56098e26 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -81,6 +81,9 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { public void onInit() { mQuickStatusBarHeaderController.init(); mView.setSceneContainerEnabled(mSceneContainerEnabled); + if (mSceneContainerEnabled && mQsPanelController != null) { + mQSPanelContainer.setOnTouchListener(null); + } } public void setListening(boolean listening) { @@ -91,13 +94,17 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { protected void onViewAttached() { mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController); mConfigurationController.addCallback(mConfigurationListener); - mQSPanelContainer.setOnTouchListener(mContainerTouchHandler); + if (!mSceneContainerEnabled && mQSPanelContainer != null) { + mQSPanelContainer.setOnTouchListener(mContainerTouchHandler); + } } @Override protected void onViewDetached() { mConfigurationController.removeCallback(mConfigurationListener); - mQSPanelContainer.setOnTouchListener(null); + if (mQSPanelContainer != null) { + mQSPanelContainer.setOnTouchListener(null); + } } public QSContainerImpl getView() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index 7f91fd2ebb80..290821e4ab13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -61,6 +61,7 @@ import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; @@ -171,8 +172,11 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private CommandQueue mCommandQueue; private View mRootView; + @Nullable private View mFooterActionsView; + private final SceneContainerFlags mSceneContainerFlags; + @Inject public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue, @@ -185,7 +189,8 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl FooterActionsViewModel.Factory footerActionsViewModelFactory, FooterActionsViewBinder footerActionsViewBinder, LargeScreenShadeInterpolator largeScreenShadeInterpolator, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + SceneContainerFlags sceneContainerFlags) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mQsMediaHost = qsMediaHost; mQqsMediaHost = qqsMediaHost; @@ -201,6 +206,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mFooterActionsViewModelFactory = footerActionsViewModelFactory; mFooterActionsViewBinder = footerActionsViewBinder; mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); + mSceneContainerFlags = sceneContainerFlags; } /** @@ -216,10 +222,17 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mQSPanelController.init(); mQuickQSPanelController.init(); - mQSFooterActionsViewModel = mFooterActionsViewModelFactory - .create(mListeningAndVisibilityLifecycleOwner); - bindFooterActionsView(mRootView); - mFooterActionsController.init(); + if (!mSceneContainerFlags.isEnabled()) { + mQSFooterActionsViewModel = mFooterActionsViewModelFactory + .create(mListeningAndVisibilityLifecycleOwner); + bindFooterActionsView(mRootView); + mFooterActionsController.init(); + } else { + View footerView = mRootView.findViewById(R.id.qs_footer_actions); + if (footerView != null) { + ((ViewGroup) footerView.getParent()).removeView(footerView); + } + } mQSPanelScrollView = mRootView.findViewById(R.id.expanded_qs_scroll_view); mQSPanelScrollView.addOnLayoutChangeListener( @@ -234,6 +247,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mScrollListener.onQsPanelScrollChanged(scrollY); } }); + mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled()); mHeader = mRootView.findViewById(R.id.header); mFooter = qsComponent.getQSFooter(); @@ -481,7 +495,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard); mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); - mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); + if (mFooterActionsView != null) { + mFooterActionsView.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); + } mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) || (mQsExpanded && !mStackScrollerOverscrolling)); mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE); @@ -622,8 +638,13 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl @Override public int getHeightDiff() { - return mQSPanelScrollView.getBottom() - mHeader.getBottom() - + mHeader.getPaddingBottom(); + if (mSceneContainerFlags.isEnabled()) { + return mQSPanelController.getViewBottom() - mHeader.getBottom() + + mHeader.getPaddingBottom(); + } else { + return mQSPanelScrollView.getBottom() - mHeader.getBottom() + + mHeader.getPaddingBottom(); + } } @Override @@ -678,25 +699,29 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); float footerActionsExpansion = onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion; - mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion, - mInSplitShade); + if (mQSFooterActionsViewModel != null) { + mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion, + mInSplitShade); + } mQSPanelController.setRevealExpansion(expansion); mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); - float qsScrollViewTranslation = - onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0; - mQSPanelScrollView.setTranslationY(qsScrollViewTranslation); + if (!mSceneContainerFlags.isEnabled()) { + float qsScrollViewTranslation = + onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0; + mQSPanelScrollView.setTranslationY(qsScrollViewTranslation); - if (fullyCollapsed) { - mQSPanelScrollView.setScrollY(0); - } + if (fullyCollapsed) { + mQSPanelScrollView.setScrollY(0); + } - if (!fullyExpanded) { - // Set bounds on the QS panel so it doesn't run over the header when animating. - mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY(); - mQsBounds.right = mQSPanelScrollView.getWidth(); - mQsBounds.bottom = mQSPanelScrollView.getHeight(); + if (!fullyExpanded) { + // Set bounds on the QS panel so it doesn't run over the header when animating. + mQsBounds.top = (int) -mQSPanelScrollView.getTranslationY(); + mQsBounds.right = mQSPanelScrollView.getWidth(); + mQsBounds.bottom = mQSPanelScrollView.getHeight(); + } } updateQsBounds(); @@ -786,15 +811,17 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin, mQSPanelScrollView.getHeight()); } - mQSPanelScrollView.setClipBounds(mQsBounds); - - mQSPanelScrollView.getLocationOnScreen(mLocationTemp); - int left = mLocationTemp[0]; - int top = mLocationTemp[1]; - mQsMediaHost.getCurrentClipping().set(left, top, - left + getView().getMeasuredWidth(), - top + mQSPanelScrollView.getMeasuredHeight() - - mQSPanelController.getPaddingBottom()); + if (!mSceneContainerFlags.isEnabled()) { + mQSPanelScrollView.setClipBounds(mQsBounds); + + mQSPanelScrollView.getLocationOnScreen(mLocationTemp); + int left = mLocationTemp[0]; + int top = mLocationTemp[1]; + mQsMediaHost.getCurrentClipping().set(left, top, + left + getView().getMeasuredWidth(), + top + mQSPanelScrollView.getMeasuredHeight() + - mQSPanelController.getPaddingBottom()); + } } private void updateMediaPositions() { @@ -867,9 +894,15 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl // The customize state changed, so our height changed. mContainer.updateExpansion(); boolean customizing = isCustomizing(); - mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); + if (mSceneContainerFlags.isEnabled()) { + mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); + } else { + mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); + } mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); - mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); + if (mFooterActionsView != null) { + mFooterActionsView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); + } mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 51b94dd983f3..7a7ee59fa63f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -387,7 +387,7 @@ public class QSPanel extends LinearLayout implements Tunable { setPaddingRelative(getPaddingStart(), mSceneContainerEnabled ? 0 : paddingTop, getPaddingEnd(), - paddingBottom); + mSceneContainerEnabled ? 0 : paddingBottom); } void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index ef58a608aa1f..c3f5086b0096 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -278,5 +278,9 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { public int getPaddingBottom() { return mView.getPaddingBottom(); } + + int getViewBottom() { + return mView.getBottom(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index ce840eec29d9..0d4339680dac 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -17,10 +17,13 @@ package com.android.systemui.qs.ui.adapter import android.content.Context +import android.content.pm.ActivityInfo import android.os.Bundle import android.view.View import androidx.annotation.VisibleForTesting import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import com.android.settingslib.applications.InterestingConfigChanges +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -58,7 +61,7 @@ interface QSSceneAdapter { /** * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in - * [qsView] + * [qsView]. Re-inflations due to configuration changes will use the last used [context]. */ suspend fun inflate(context: Context) @@ -90,6 +93,7 @@ constructor( private val qsImplProvider: Provider<QSImpl>, @Main private val mainDispatcher: CoroutineDispatcher, @Application applicationScope: CoroutineScope, + private val configurationInteractor: ConfigurationInteractor, private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater, ) : QSContainerController, QSSceneAdapter { @@ -99,7 +103,15 @@ constructor( qsImplProvider: Provider<QSImpl>, @Main dispatcher: CoroutineDispatcher, @Application scope: CoroutineScope, - ) : this(qsSceneComponentFactory, qsImplProvider, dispatcher, scope, ::AsyncLayoutInflater) + configurationInteractor: ConfigurationInteractor, + ) : this( + qsSceneComponentFactory, + qsImplProvider, + dispatcher, + scope, + configurationInteractor, + ::AsyncLayoutInflater, + ) private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED) private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false) @@ -109,14 +121,36 @@ constructor( val qsImpl = _qsImpl.asStateFlow() override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull() + // Same config changes as in FragmentHostManager + private val interestingChanges = + InterestingConfigChanges( + ActivityInfo.CONFIG_FONT_SCALE or + ActivityInfo.CONFIG_LOCALE or + ActivityInfo.CONFIG_ASSETS_PATHS + ) + init { applicationScope.launch { - state.sample(_isCustomizing, ::Pair).collect { (state, customizing) -> - _qsImpl.value?.apply { - if (state != QSSceneAdapter.State.QS && customizing) { - this@apply.closeCustomizerImmediately() + launch { + state.sample(_isCustomizing, ::Pair).collect { (state, customizing) -> + _qsImpl.value?.apply { + if (state != QSSceneAdapter.State.QS && customizing) { + this@apply.closeCustomizerImmediately() + } + applyState(state) + } + } + } + launch { + configurationInteractor.configurationValues.collect { config -> + if (interestingChanges.applyNewConfig(config)) { + // Assumption: The context is always the same and with the same theme. + // If colors change they will be reflected as attributes in the theme. + qsImpl.value?.view?.let { inflate(it.context) } + } else { + qsImpl.value?.onConfigurationChanged(config) + qsImpl.value?.view?.dispatchConfigurationChanged(config) } - applyState(state) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index e5e1e8445e94..8a900ece2750 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -16,7 +16,10 @@ package com.android.systemui.qs.ui.viewmodel +import androidx.lifecycle.LifecycleOwner import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.FooterActionsController +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -24,6 +27,7 @@ import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlinx.coroutines.flow.map @@ -35,6 +39,8 @@ constructor( val shadeHeaderViewModel: ShadeHeaderViewModel, val qsSceneAdapter: QSSceneAdapter, val notifications: NotificationsPlaceholderViewModel, + private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, + private val footerActionsController: FooterActionsController, ) { val destinationScenes = qsSceneAdapter.isCustomizing.map { customizing -> @@ -47,4 +53,13 @@ constructor( ) } } + + private val footerActionsControllerInitialized = AtomicBoolean(false) + + fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { + if (footerActionsControllerInitialized.compareAndSet(false, true)) { + footerActionsController.init() + } + return footerActionsViewModelFactory.create(lifecycleOwner) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 3a59978d415c..46ddba4d4d8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -65,9 +65,7 @@ public abstract class NotificationRowModule { CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory ) { final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>(); - if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) { - replacementFactories.add(precomputedTextViewFactory); - } + replacementFactories.add(precomputedTextViewFactory); if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) { replacementFactories.add(bigPictureLayoutInflaterFactory); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 459b368b5ac9..aabe4a0d66f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -300,7 +300,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump DozeParameters dozeParameters, AlarmManager alarmManager, KeyguardStateController keyguardStateController, - DelayedWakeLock.Builder delayedWakeLockBuilder, + DelayedWakeLock.Factory delayedWakeLockFactory, Handler handler, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, @@ -331,7 +331,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScreenOffAnimationController = screenOffAnimationController; mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout, "hide_aod_wallpaper", mHandler); - mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build(); + mWakeLock = delayedWakeLockFactory.create("Scrims"); // Scrim alpha is initially set to the value on the resource but might be changed // to make sure that text on top of it is legible. mDozeParameters = dozeParameters; diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java index 972895d4a192..039109e9ddc6 100644 --- a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java +++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java @@ -19,7 +19,11 @@ package com.android.systemui.util.wakelock; import android.content.Context; import android.os.Handler; -import javax.inject.Inject; +import com.android.systemui.dagger.qualifiers.Background; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; /** * A wake lock that has a built in delay when releasing to give the framebuffer time to update. @@ -32,9 +36,11 @@ public class DelayedWakeLock implements WakeLock { private final Handler mHandler; private final WakeLock mInner; - public DelayedWakeLock(Handler h, WakeLock inner) { - mHandler = h; - mInner = inner; + @AssistedInject + public DelayedWakeLock(@Background Handler handler, Context context, WakeLockLogger logger, + @Assisted String tag) { + mInner = WakeLock.createPartial(context, logger, tag); + mHandler = handler; } @Override @@ -58,46 +64,11 @@ public class DelayedWakeLock implements WakeLock { } /** - * An injectable builder for {@see DelayedWakeLock} that has the context already filled in. + * Factory to create the instance of DelayedWakeLock class. */ - public static class Builder { - private final Context mContext; - private final WakeLockLogger mLogger; - private String mTag; - private Handler mHandler; - - /** - * Constructor for DelayedWakeLock.Builder - */ - @Inject - public Builder(Context context, WakeLockLogger logger) { - mContext = context; - mLogger = logger; - } - - /** - * Set the tag for the WakeLock. - */ - public Builder setTag(String tag) { - mTag = tag; - - return this; - } - - /** - * Set the handler for the DelayedWakeLock. - */ - public Builder setHandler(Handler handler) { - mHandler = handler; - - return this; - } - - /** - * Build the DelayedWakeLock. - */ - public DelayedWakeLock build() { - return new DelayedWakeLock(mHandler, WakeLock.createPartial(mContext, mLogger, mTag)); - } + @AssistedFactory + public interface Factory { + /** creates the instance of DelayedWakeLock class. */ + DelayedWakeLock create(String tag); } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 9ee3d220a79b..aee441a13a5d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -535,6 +535,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } if (changed && fromKey) { Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume); + mCallbacks.onVolumeChangedFromKey(); } return changed; } @@ -1030,6 +1031,18 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } @Override + public void onVolumeChangedFromKey() { + for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { + entry.getValue().post(new Runnable() { + @Override + public void run() { + entry.getKey().onVolumeChangedFromKey(); + } + }); + } + } + + @Override public void onAccessibilityModeChanged(Boolean showA11yStream) { boolean show = showA11yStream != null && showA11yStream; for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 404621d1fe81..b127c5160bba 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; +import static com.android.systemui.Flags.hapticVolumeSlider; import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -117,7 +118,11 @@ import com.android.internal.view.RotationPolicy; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.Prefs; +import com.android.systemui.dagger.qualifiers.Application; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin; +import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; @@ -125,6 +130,7 @@ import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.res.R; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; @@ -140,6 +146,9 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; + /** * Visual presentation of the volume dialog. * @@ -303,6 +312,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private int mOrientation; private final Lazy<SecureSettings> mSecureSettings; private int mDialogTimeoutMillis; + private final CoroutineDispatcher mMainDispatcher; + private final CoroutineScope mApplicationScope; + private final VibratorHelper mVibratorHelper; + private final com.android.systemui.util.time.SystemClock mSystemClock; public VolumeDialogImpl( Context context, @@ -319,11 +332,18 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, DevicePostureController devicePostureController, Looper looper, DumpManager dumpManager, - Lazy<SecureSettings> secureSettings) { + Lazy<SecureSettings> secureSettings, + VibratorHelper vibratorHelper, + @Main CoroutineDispatcher mainDispatcher, + @Application CoroutineScope applicationScope, + com.android.systemui.util.time.SystemClock systemClock) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); mHandler = new H(looper); - + mMainDispatcher = mainDispatcher; + mApplicationScope = applicationScope; + mVibratorHelper = vibratorHelper; + mSystemClock = systemClock; mShouldListenForJank = shouldListenForJank; mController = volumeDialogController; mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); @@ -839,6 +859,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)}); } row.slider = row.view.findViewById(R.id.volume_row_slider); + row.createPlugin(mVibratorHelper, mSystemClock, mMainDispatcher, mApplicationScope); row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row)); row.number = row.view.findViewById(R.id.volume_number); @@ -1480,6 +1501,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mController.getCaptionsComponentState(false); checkODICaptionsTooltip(false); updateBackgroundForDrawerClosedAmount(); + for (int i = 0; i < mRows.size(); i++) { + VolumeRow row = mRows.get(i); + if (row.slider.getVisibility() == VISIBLE) { + row.addHaptics(); + } + } Trace.endSection(); } @@ -1532,7 +1559,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, protected void dismissH(int reason) { Trace.beginSection("VolumeDialogImpl#dismissH"); - + for (int i = 0; i < mRows.size(); i++) { + mRows.get(i).removeHaptics(); + } Log.i(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason] + " from: " + Debug.getCaller()); @@ -2358,6 +2387,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) { updateCaptionsEnabledH(isEnabled, checkForSwitchState); } + + @Override + public void onVolumeChangedFromKey() { + VolumeRow activeRow = getActiveRow(); + if (activeRow.mHapticPlugin != null) { + activeRow.mHapticPlugin.onKeyDown(); + } + } }; @VisibleForTesting void onPostureChanged(int posture) { @@ -2459,6 +2496,15 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (mRow.ss == null) return; + if (getActiveRow().equals(mRow) + && mRow.slider.getVisibility() == VISIBLE + && mRow.mHapticPlugin != null) { + mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser); + if (!fromUser) { + // Consider a change from program as the volume key being continuously pressed + mRow.mHapticPlugin.onKeyDown(); + } + } if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream) + " onProgressChanged " + progress + " fromUser=" + fromUser); if (!fromUser) return; @@ -2485,6 +2531,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @Override public void onStartTrackingTouch(SeekBar seekBar) { if (D.BUG) Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream); + if (mRow.mHapticPlugin != null) { + mRow.mHapticPlugin.onStartTrackingTouch(seekBar); + } mController.setActiveStream(mRow.stream); mRow.tracking = true; } @@ -2492,6 +2541,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @Override public void onStopTrackingTouch(SeekBar seekBar) { if (D.BUG) Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream); + if (mRow.mHapticPlugin != null) { + mRow.mHapticPlugin.onStopTrackingTouch(seekBar); + } mRow.tracking = false; mRow.userAttempt = SystemClock.uptimeMillis(); final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress()); @@ -2524,6 +2576,22 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } private static class VolumeRow { + private static final SliderHapticFeedbackConfig sSliderHapticFeedbackConfig = + new SliderHapticFeedbackConfig( + /* velocityInterpolatorFactor= */ 1f, + /* progressInterpolatorFactor= */ 1f, + /* progressBasedDragMinScale= */ 0f, + /* progressBasedDragMaxScale= */ 0.2f, + /* additionalVelocityMaxBump= */ 0.15f, + /* deltaMillisForDragInterval= */ 0f, + /* deltaProgressForDragThreshold= */ 0.015f, + /* numberOfLowTicks= */ 5, + /* maxVelocityToScale= */ 300f, + /* velocityAxis= */ MotionEvent.AXIS_Y, + /* upperBookendScale= */ 1f, + /* lowerBookendScale= */ 0.05f, + /* exponent= */ 1f / 0.89f); + private View view; private TextView header; private ImageButton icon; @@ -2544,6 +2612,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private ObjectAnimator anim; // slider progress animation for non-touch-related updates private int animTargetProgress; private int lastAudibleLevel = 1; + private SeekableSliderHapticPlugin mHapticPlugin; void setIcon(int iconRes, Resources.Theme theme) { if (icon != null) { @@ -2554,6 +2623,50 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); } } + + void createPlugin( + VibratorHelper vibratorHelper, + com.android.systemui.util.time.SystemClock systemClock, + CoroutineDispatcher mainDispatcher, + CoroutineScope applicationScope) { + if (!hapticVolumeSlider() || mHapticPlugin != null) return; + + mHapticPlugin = new SeekableSliderHapticPlugin( + vibratorHelper, + systemClock, + mainDispatcher, + applicationScope, + sSliderHapticFeedbackConfig); + } + + + @SuppressLint("ClickableViewAccessibility") + void addTouchListener() { + slider.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + if (mHapticPlugin != null) { + mHapticPlugin.onTouchEvent(motionEvent); + } + return false; + } + }); + } + + void addHaptics() { + if (mHapticPlugin != null) { + addTouchListener(); + mHapticPlugin.start(); + } + } + + @SuppressLint("ClickableViewAccessibility") + void removeHaptics() { + slider.setOnTouchListener(null); + if (mHapticPlugin != null) { + mHapticPlugin.stop(); + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index 497c4cb070f0..f180a942af70 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -22,16 +22,20 @@ import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.CoreStartable; +import com.android.systemui.dagger.qualifiers.Application; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.util.time.SystemClock; import com.android.systemui.volume.CsdWarningDialog; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; @@ -49,6 +53,9 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; import dagger.multibindings.IntoSet; +import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.CoroutineScope; + /** Dagger Module for code in the volume package. */ @Module( subcomponents = { @@ -90,7 +97,11 @@ public interface VolumeModule { CsdWarningDialog.Factory csdFactory, DevicePostureController devicePostureController, DumpManager dumpManager, - Lazy<SecureSettings> secureSettings) { + Lazy<SecureSettings> secureSettings, + VibratorHelper vibratorHelper, + @Main CoroutineDispatcher mainDispatcher, + @Application CoroutineScope applicationScope, + SystemClock systemClock) { VolumeDialogImpl impl = new VolumeDialogImpl( context, volumeDialogController, @@ -106,7 +117,11 @@ public interface VolumeModule { devicePostureController, Looper.getMainLooper(), dumpManager, - secureSettings); + secureSettings, + vibratorHelper, + mainDispatcher, + applicationScope, + systemClock); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); impl.setSilentMode(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt new file mode 100644 index 000000000000..df73cc8f0212 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.ui + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository +import com.android.systemui.keyboard.data.repository.keyboardRepository +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.phone.ComponentSystemUIDialog +import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import org.junit.Assume +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() { + + private lateinit var coordinator: StickyKeysIndicatorCoordinator + private val testScope = TestScope(StandardTestDispatcher()) + private val stickyKeysRepository = FakeStickyKeysRepository() + private val dialog = mock<ComponentSystemUIDialog>() + + @Before + fun setup() { + Assume.assumeTrue(ComposeFacade.isComposeAvailable()) + val dialogFactory = mock<SystemUIDialogFactory> { + whenever(applicationContext).thenReturn(context) + whenever(create(any(), anyInt(), anyBoolean())).thenReturn(dialog) + } + val keyboardRepository = Kosmos().keyboardRepository + val viewModel = StickyKeysIndicatorViewModel( + stickyKeysRepository, + keyboardRepository, + testScope.backgroundScope) + coordinator = StickyKeysIndicatorCoordinator( + testScope.backgroundScope, + dialogFactory, + viewModel, + mock<StickyKeysLogger>()) + coordinator.startListening() + keyboardRepository.setIsAnyKeyboardConnected(true) + } + + @Test + fun dialogIsShownWhenStickyKeysAreEmitted() { + testScope.run { + verifyZeroInteractions(dialog) + + stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true))) + runCurrent() + + verify(dialog).show() + } + } + + @Test + fun dialogDisappearsWhenStickyKeysAreEmpty() { + testScope.run { + verifyZeroInteractions(dialog) + + stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true))) + runCurrent() + stickyKeysRepository.setStickyKeys(linkedMapOf()) + runCurrent() + + verify(dialog).dismiss() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt index d397fc202637..8a713688acf9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -46,6 +47,7 @@ import org.mockito.ArgumentCaptor import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class StickyKeysIndicatorViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java index c8c134a9474a..563a3fe9fc7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.annotation.Nullable; @@ -47,6 +48,7 @@ import android.view.Display; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import androidx.lifecycle.Lifecycle; import androidx.test.filters.SmallTest; @@ -63,6 +65,7 @@ import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.CommandQueue; @@ -111,7 +114,8 @@ public class QSImplTest extends SysuiTestCase { @Mock private FooterActionsViewBinder mFooterActionsViewBinder; @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; @Mock private FeatureFlagsClassic mFeatureFlags; - private View mQsView; + @Mock private SceneContainerFlags mSceneContainerFlags; + private ViewGroup mQsView; private final CommandQueue mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext)); @@ -121,6 +125,9 @@ public class QSImplTest extends SysuiTestCase { @Before public void setup() { + MockitoAnnotations.initMocks(this); + when(mSceneContainerFlags.isEnabled()).thenReturn(false); + mUnderTest = instantiate(); mUnderTest.onComponentCreated(mQsComponent, null); @@ -487,9 +494,24 @@ public class QSImplTest extends SysuiTestCase { verify(mQSAnimator).setOnKeyguard(true); } - private QSImpl instantiate() { - MockitoAnnotations.initMocks(this); + @Test + public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() { + when(mSceneContainerFlags.isEnabled()).thenReturn(true); + clearInvocations( + mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory); + QSImpl other = instantiate(); + + other.onComponentCreated(mQsComponent, null); + assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull(); + verifyZeroInteractions( + mFooterActionsViewModel, + mFooterActionsViewBinder, + mFooterActionsViewModelFactory + ); + } + + private QSImpl instantiate() { setupQsComponent(); setUpViews(); setUpInflater(); @@ -514,7 +536,8 @@ public class QSImplTest extends SysuiTestCase { mFooterActionsViewModelFactory, mFooterActionsViewBinder, mLargeScreenShadeInterpolator, - mFeatureFlags); + mFeatureFlags, + mSceneContainerFlags); } private void setUpOther() { @@ -533,14 +556,23 @@ public class QSImplTest extends SysuiTestCase { } private void setUpViews() { - mQsView = spy(new View(mContext)); + mQsView = spy(new FrameLayout(mContext)); when(mQsComponent.getRootView()).thenReturn(mQsView); - when(mQsView.findViewById(R.id.expanded_qs_scroll_view)) + + when(mQSPanelScrollView.findViewById(R.id.expanded_qs_scroll_view)) .thenReturn(mQSPanelScrollView); - when(mQsView.findViewById(R.id.header)).thenReturn(mHeader); - when(mQsView.findViewById(android.R.id.edit)).thenReturn(new View(mContext)); - when(mQsView.findViewById(R.id.qs_footer_actions)).thenAnswer( - invocation -> new FooterActionsViewBinder().create(mContext)); + mQsView.addView(mQSPanelScrollView); + + when(mHeader.findViewById(R.id.header)).thenReturn(mHeader); + mQsView.addView(mHeader); + + View customizer = new View(mContext); + customizer.setId(android.R.id.edit); + mQsView.addView(customizer); + + View footerActionsView = new FooterActionsViewBinder().create(mContext); + footerActionsView.setId(R.id.qs_footer_actions); + mQsView.addView(footerActionsView); } private void setUpInflater() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index d9eaea1367cd..b3a47d77a307 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -48,7 +48,6 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; -import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.MathUtils; @@ -135,7 +134,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Mock private AlarmManager mAlarmManager; @Mock private DozeParameters mDozeParameters; @Mock private LightBarController mLightBarController; - @Mock private DelayedWakeLock.Builder mDelayedWakeLockBuilder; + @Mock private DelayedWakeLock.Factory mDelayedWakeLockFactory; @Mock private DelayedWakeLock mWakeLock; @Mock private KeyguardStateController mKeyguardStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -262,11 +261,7 @@ public class ScrimControllerTest extends SysuiTestCase { }).when(mLightBarController).setScrimState( any(ScrimState.class), anyFloat(), any(GradientColors.class)); - when(mDelayedWakeLockBuilder.setHandler(any(Handler.class))) - .thenReturn(mDelayedWakeLockBuilder); - when(mDelayedWakeLockBuilder.setTag(any(String.class))) - .thenReturn(mDelayedWakeLockBuilder); - when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock); + when(mDelayedWakeLockFactory.create(any(String.class))).thenReturn(mWakeLock); when(mDockManager.isDocked()).thenReturn(false); when(mKeyguardTransitionInteractor.transition(any(), any())) @@ -281,7 +276,7 @@ public class ScrimControllerTest extends SysuiTestCase { mDozeParameters, mAlarmManager, mKeyguardStateController, - mDelayedWakeLockBuilder, + mDelayedWakeLockFactory, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, mDockManager, @@ -990,7 +985,7 @@ public class ScrimControllerTest extends SysuiTestCase { mDozeParameters, mAlarmManager, mKeyguardStateController, - mDelayedWakeLockBuilder, + mDelayedWakeLockFactory, new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor, mDockManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 8c823b2376c3..d839da1d6e1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -32,6 +32,7 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assume.assumeNotNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -63,6 +64,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.testing.UiEventLoggerFake; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; @@ -72,6 +74,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.res.R; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; @@ -79,6 +82,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.util.time.FakeSystemClock; import dagger.Lazy; @@ -97,6 +101,8 @@ import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.function.Predicate; +import kotlinx.coroutines.Dispatchers; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -138,7 +144,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { DevicePostureController mPostureController; @Mock private Lazy<SecureSettings> mLazySecureSettings; - private final CsdWarningDialog.Factory mCsdWarningDialogFactory = new CsdWarningDialog.Factory() { @Override @@ -146,6 +151,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { return mCsdWarningDialog; } }; + @Mock + private VibratorHelper mVibratorHelper; private int mLongestHideShowAnimationDuration = 250; private FakeSettings mSecureSettings; @@ -180,6 +187,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { when(mLazySecureSettings.get()).thenReturn(mSecureSettings); + when(mVibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(new int[]{0}); + mDialog = new VolumeDialogImpl( getContext(), mVolumeDialogController, @@ -195,7 +204,11 @@ public class VolumeDialogImplTest extends SysuiTestCase { mPostureController, mTestableLooper.getLooper(), mDumpManager, - mLazySecureSettings); + mLazySecureSettings, + mVibratorHelper, + Dispatchers.getUnconfined(), + TestScopeProvider.getTestScope(), + new FakeSystemClock()); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); diff --git a/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt new file mode 100644 index 000000000000..e5121d5de6dd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/app/KeyguardManagerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.keyguardManager by Kosmos.Fixture { mock<KeyguardManager>() } diff --git a/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt b/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt new file mode 100644 index 000000000000..4e2683bd7a2f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/os/HandlerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os + +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.concurrency.mockExecutorHandler + +val Kosmos.fakeExecutorHandler by Kosmos.Fixture { mockExecutorHandler(fakeExecutor) } diff --git a/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt new file mode 100644 index 000000000000..fb51f0fec997 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/service/dream/DreamManagerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.dream + +import android.service.dreams.IDreamManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.dreamManager by Kosmos.Fixture { mock<IDreamManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt new file mode 100644 index 000000000000..d9ea5e92710c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/internal/widget/LockPatternUtilsKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.lockPatternUtils by Kosmos.Fixture { mock<LockPatternUtils>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt new file mode 100644 index 000000000000..7185b7cd0ac6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/ActivityIntentHelperKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos + +val Kosmos.activityIntentHelper by Kosmos.Fixture { ActivityIntentHelper(applicationContext) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt new file mode 100644 index 000000000000..128f58bf9751 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.animation + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt new file mode 100644 index 000000000000..b7d6f3a5f91f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.assist + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.assistManager by Kosmos.Fixture { mock<AssistManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt new file mode 100644 index 000000000000..68e14573a9b2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeStickyKeysRepository.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.data.repository + +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeStickyKeysRepository : StickyKeysRepository { + override val settingEnabled: Flow<Boolean> = MutableStateFlow(true) + private val _stickyKeys: MutableStateFlow<LinkedHashMap<ModifierKey, Locked>> = + MutableStateFlow(LinkedHashMap()) + + override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> = _stickyKeys + + fun setStickyKeys(keys: LinkedHashMap<ModifierKey, Locked>) { + _stickyKeys.value = keys + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt new file mode 100644 index 000000000000..46f7355dfe75 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.keyboardRepository by Kosmos.Fixture { FakeKeyboardRepository() }
\ No newline at end of file diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt new file mode 100644 index 000000000000..7cc5d6b6243a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.shadeController by Kosmos.Fixture { mock<ShadeController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt new file mode 100644 index 000000000000..1ceab68604f3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.shadeViewController by Kosmos.Fixture { mock<ShadeViewController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt new file mode 100644 index 000000000000..4dcd2208b152 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeAnimationRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.shadeAnimationRepository by Kosmos.Fixture { ShadeAnimationRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt new file mode 100644 index 000000000000..57b272f10c67 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shade.data.repository.shadeAnimationRepository + +var Kosmos.shadeAnimationInteractor: ShadeAnimationInteractor by + Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt new file mode 100644 index 000000000000..a75d2bc4aaf6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryKosmos.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.notifications.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.shared.settings.data.repository.secureSettingsRepository + +val Kosmos.notificationSettingsRepository by + Kosmos.Fixture { + NotificationSettingsRepository( + scope = testScope.backgroundScope, + backgroundDispatcher = testDispatcher, + secureSettingsRepository = secureSettingsRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt new file mode 100644 index 000000000000..17b4603e17b9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.notifications.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository + +val Kosmos.notificationSettingsInteractor by + Kosmos.Fixture { NotificationSettingsInteractor(notificationSettingsRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt new file mode 100644 index 000000000000..552b09e933de --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.settings.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.secureSettingsRepository: SecureSettingsRepository by + Kosmos.Fixture { fakeSecureSettingsRepository } +val Kosmos.fakeSecureSettingsRepository by Kosmos.Fixture { FakeSecureSettingsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt new file mode 100644 index 000000000000..7b912aef3011 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationClickNotifierKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.notificationClickNotifier by Kosmos.Fixture { mock<NotificationClickNotifier>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt new file mode 100644 index 000000000000..8d30049bfb09 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationPresenterKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.notificationPresenter by Kosmos.Fixture { mock<NotificationPresenter>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt new file mode 100644 index 000000000000..554bdbe0c382 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.notificationRemoteInputManager by + Kosmos.Fixture { mock<NotificationRemoteInputManager>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt new file mode 100644 index 000000000000..e8ca3b8234e8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationShadeWindowControllerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.notificationShadeWindowController by + Kosmos.Fixture { mock<NotificationShadeWindowController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt new file mode 100644 index 000000000000..c337ac201b3d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationActivityStarterKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.phone.statusBarNotificationActivityStarter + +var Kosmos.notificationActivityStarter: NotificationActivityStarter by + Kosmos.Fixture { statusBarNotificationActivityStarter } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt new file mode 100644 index 000000000000..c3db34bdddb7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.notificationLaunchAnimatorControllerProvider by + Kosmos.Fixture { mock<NotificationLaunchAnimatorControllerProvider>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt new file mode 100644 index 000000000000..1f45fbbcf927 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection + +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos + +var Kosmos.notifLiveDataStore: NotifLiveDataStore by + Kosmos.Fixture { NotifLiveDataStoreImpl(fakeExecutor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt new file mode 100644 index 000000000000..358d2519556b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.visualStabilityCoordinator by Kosmos.Fixture { mock<VisualStabilityCoordinator>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt new file mode 100644 index 000000000000..a5c956155351 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProviderKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.provider + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.launchFullScreenIntentProvider by Kosmos.Fixture { LaunchFullScreenIntentProvider() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt new file mode 100644 index 000000000000..edce5d58b351 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderKosmos.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.collection.notifLiveDataStore +import com.android.systemui.statusbar.notification.collection.notifPipeline +import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor + +var Kosmos.notificationVisibilityProvider: NotificationVisibilityProvider by + Kosmos.Fixture { + NotificationVisibilityProviderImpl( + activeNotificationsInteractor, + notifLiveDataStore, + notifPipeline, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt new file mode 100644 index 000000000000..1e3897ba46c6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.statusbar.notification.collection.coordinator.visualStabilityCoordinator +import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl +import com.android.systemui.statusbar.notification.collection.notifCollection +import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider +import com.android.systemui.statusbar.policy.headsUpManager + +var Kosmos.onUserInteractionCallback: OnUserInteractionCallback by + Kosmos.Fixture { + OnUserInteractionCallbackImpl( + notificationVisibilityProvider, + notifCollection, + headsUpManager, + statusBarStateController, + visualStabilityCoordinator, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt new file mode 100644 index 000000000000..6ddc9df930f3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.app.keyguardManager +import android.content.applicationContext +import android.os.fakeExecutorHandler +import android.service.dream.dreamManager +import com.android.internal.logging.metricsLogger +import com.android.internal.widget.lockPatternUtils +import com.android.systemui.activityIntentHelper +import com.android.systemui.animation.activityLaunchAnimator +import com.android.systemui.assist.assistManager +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.activityStarter +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.settings.userTracker +import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor +import com.android.systemui.shade.shadeController +import com.android.systemui.shade.shadeViewController +import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider +import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider +import com.android.systemui.statusbar.notification.notificationLaunchAnimatorControllerProvider +import com.android.systemui.statusbar.notification.row.onUserInteractionCallback +import com.android.systemui.statusbar.notificationClickNotifier +import com.android.systemui.statusbar.notificationLockscreenUserManager +import com.android.systemui.statusbar.notificationPresenter +import com.android.systemui.statusbar.notificationRemoteInputManager +import com.android.systemui.statusbar.notificationShadeWindowController +import com.android.systemui.statusbar.policy.headsUpManager +import com.android.systemui.statusbar.policy.keyguardStateController +import com.android.systemui.wmshell.bubblesManager +import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +val Kosmos.statusBarNotificationActivityStarter by + Kosmos.Fixture { + StatusBarNotificationActivityStarter( + applicationContext, + applicationContext.displayId, + fakeExecutorHandler, + fakeExecutor, + notificationVisibilityProvider, + headsUpManager, + activityStarter, + notificationClickNotifier, + statusBarKeyguardViewManager, + keyguardManager, + dreamManager, + Optional.of(bubblesManager), + { assistManager }, + notificationRemoteInputManager, + notificationLockscreenUserManager, + shadeController, + keyguardStateController, + lockPatternUtils, + statusBarRemoteInputCallback, + activityIntentHelper, + metricsLogger, + statusBarNotificationActivityStarterLogger, + onUserInteractionCallback, + notificationPresenter, + shadeViewController, + notificationShadeWindowController, + activityLaunchAnimator, + shadeAnimationInteractor, + notificationLaunchAnimatorControllerProvider, + launchFullScreenIntentProvider, + powerInteractor, + userTracker, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt new file mode 100644 index 000000000000..31cfc979a11a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLoggerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.logcatLogBuffer + +val Kosmos.statusBarNotificationActivityStarterLogger by + Kosmos.Fixture { StatusBarNotificationActivityStarterLogger(logcatLogBuffer()) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt new file mode 100644 index 000000000000..475d7fa6ef4b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.statusBarRemoteInputCallback by Kosmos.Fixture { mock<StatusBarRemoteInputCallback>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt new file mode 100644 index 000000000000..0e909c498a2b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/KeyguardStateControllerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.keyguardStateController by Kosmos.Fixture { mock<KeyguardStateController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt new file mode 100644 index 000000000000..1d05d62a02e4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wmshell/BubblesManagerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.wmshell + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.bubblesManager by Kosmos.Fixture { mock<BubblesManager>() } diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml new file mode 100644 index 000000000000..be1f081d5b8f --- /dev/null +++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <!-- If true, attach the navigation bar to the app during app transition --> + <bool name="config_attachNavBarToAppDuringTransition">false</bool> +</resources> diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml new file mode 100644 index 000000000000..be1f081d5b8f --- /dev/null +++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <!-- If true, attach the navigation bar to the app during app transition --> + <bool name="config_attachNavBarToAppDuringTransition">false</bool> +</resources> diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml new file mode 100644 index 000000000000..be1f081d5b8f --- /dev/null +++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <!-- If true, attach the navigation bar to the app during app transition --> + <bool name="config_attachNavBarToAppDuringTransition">false</bool> +</resources> diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml new file mode 100644 index 000000000000..be1f081d5b8f --- /dev/null +++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <!-- If true, attach the navigation bar to the app during app transition --> + <bool name="config_attachNavBarToAppDuringTransition">false</bool> +</resources> diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 3d8d7b738233..d656892062d1 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -4413,25 +4413,50 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) { mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); + final ComponentName componentName = info.getComponentName(); // Warning is not required if the service is already enabled. synchronized (mLock) { final AccessibilityUserState userState = getCurrentUserStateLocked(); - if (userState.getEnabledServicesLocked().contains(info.getComponentName())) { + if (userState.getEnabledServicesLocked().contains(componentName)) { return false; } } // Warning is not required if the service is already assigned to a shortcut. for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) { if (getAccessibilityShortcutTargets(shortcutType).contains( - info.getComponentName().flattenToString())) { + componentName.flattenToString())) { return false; } } + // Warning is not required if the service is preinstalled and in the + // trustedAccessibilityServices allowlist. + if (android.view.accessibility.Flags.skipAccessibilityWarningDialogForTrustedServices() + && isAccessibilityServicePreinstalledAndTrusted(info)) { + return false; + } + // Warning is required by default. return true; } + private boolean isAccessibilityServicePreinstalledAndTrusted(AccessibilityServiceInfo info) { + final ComponentName componentName = info.getComponentName(); + final boolean isPreinstalled = + info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp(); + if (isPreinstalled) { + final String[] trustedAccessibilityServices = + mContext.getResources().getStringArray( + R.array.config_trustedAccessibilityServices); + if (Arrays.stream(trustedAccessibilityServices) + .map(ComponentName::unflattenFromString) + .anyMatch(componentName::equals)) { + return true; + } + } + return false; + } + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 42ab05fdd231..4d42f154a392 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -58,11 +58,13 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.view.autofill.AutofillManager; import android.widget.ImageView; import android.widget.RemoteViews; +import android.widget.ScrollView; import android.widget.TextView; import com.android.internal.R; @@ -370,9 +372,23 @@ final class SaveUi { params.windowAnimations = R.style.AutofillSaveAnimation; params.setTrustedOverlay(); + ScrollView scrollView = view.findViewById(R.id.autofill_sheet_scroll_view); + + View divider = view.findViewById(R.id.autofill_sheet_divider); + + ViewTreeObserver observer = scrollView.getViewTreeObserver(); + observer.addOnGlobalLayoutListener(() -> adjustDividerVisibility(scrollView, divider)); + + scrollView.getViewTreeObserver() + .addOnScrollChangedListener(() -> adjustDividerVisibility(scrollView, divider)); show(); } + private void adjustDividerVisibility(ScrollView scrollView, View divider) { + boolean canScrollDown = scrollView.canScrollVertically(1); // 1 to check scrolling down + divider.setVisibility(canScrollDown ? View.VISIBLE : View.INVISIBLE); + } + private boolean applyCustomDescription(@NonNull Context context, @NonNull View saveUiView, @NonNull ValueFinder valueFinder, @NonNull SaveInfo info) { final CustomDescription customDescription = info.getCustomDescription(); diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig index 549fa36597b7..4022e3378954 100644 --- a/services/backup/flags.aconfig +++ b/services/backup/flags.aconfig @@ -10,6 +10,15 @@ flag { } flag { + name: "enable_metrics_system_backup_agents" + namespace: "backup" + description: "Enable SystemBackupAgent to collect B&R agent metrics by passing an instance of " + "the logger to each BackupHelper." + bug: "296844513" + is_fixed_read_only: true +} + +flag { name: "enable_max_size_writes_to_pipes" namespace: "onboarding" description: "Enables the write buffer to pipes to be of maximum size." diff --git a/services/core/Android.bp b/services/core/Android.bp index fdcd27da5bdc..3164e083af0f 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -211,6 +211,7 @@ java_library_static { "com_android_wm_shell_flags_lib", "com.android.server.utils_aconfig-java", "service-jobscheduler-deviceidle.flags-aconfig-java", + "backup_flags_lib", "policy_flags_lib", ], javac_shard_size: 50, diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java index de4979a0c826..5b9469bc5610 100644 --- a/services/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java @@ -20,7 +20,8 @@ import android.app.IWallpaperManager; import android.app.backup.BackupAgentHelper; import android.app.backup.BackupAnnotations.BackupDestination; import android.app.backup.BackupDataInput; -import android.app.backup.BackupHelper; +import android.app.backup.BackupHelperWithLogger; +import android.app.backup.BackupRestoreEventLogger; import android.app.backup.FullBackup; import android.app.backup.FullBackupDataOutput; import android.app.backup.WallpaperBackupHelper; @@ -33,9 +34,10 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; - import com.google.android.collect.Sets; +import com.android.server.backup.Flags; + import java.io.File; import java.io.IOException; import java.util.Set; @@ -107,10 +109,12 @@ public class SystemBackupAgent extends BackupAgentHelper { private int mUserId = UserHandle.USER_SYSTEM; private boolean mIsProfileUser = false; + private BackupRestoreEventLogger mLogger; @Override public void onCreate(UserHandle user, @BackupDestination int backupDestination) { super.onCreate(user, backupDestination); + mLogger = this.getBackupRestoreEventLogger(); mUserId = user.getIdentifier(); if (mUserId != UserHandle.USER_SYSTEM) { @@ -209,9 +213,12 @@ public class SystemBackupAgent extends BackupAgentHelper { } } - private void addHelperIfEligibleForUser(String keyPrefix, BackupHelper helper) { + private void addHelperIfEligibleForUser(String keyPrefix, BackupHelperWithLogger helper) { if (isHelperEligibleForUser(keyPrefix)) { addHelper(keyPrefix, helper); + if (Flags.enableMetricsSystemBackupAgents()) { + helper.setLogger(mLogger); + } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index d34661d4d6ac..34e75c087864 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -47,6 +47,7 @@ import android.media.AudioDeviceInfo; import android.media.AudioProfile; import android.media.tv.TvInputInfo; import android.media.tv.TvInputManager.TvInputCallback; +import android.os.Handler; import android.util.Slog; import android.util.SparseBooleanArray; @@ -97,9 +98,15 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { private boolean mSystemAudioMute = false; // If true, do not do routing control/send active source for internal source. - // Set to true when the device was woken up by <Text/Image View On>. + // Set to true for a short duration when the device is woken up by <Text/Image View On>. private boolean mSkipRoutingControl; + // Handler for posting a runnable to set `mSkipRoutingControl` to false after a delay + private final Handler mSkipRoutingControlHandler; + + // Runnable that sets `mSkipRoutingControl` to false + private final Runnable mResetSkipRoutingControlRunnable = () -> mSkipRoutingControl = false; + // Message buffer used to buffer selected messages to process later. <Active Source> // from a source device, for instance, needs to be buffered if the device is not // discovered yet. The buffered commands are taken out and when they are ready to @@ -162,6 +169,7 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL) == HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED; mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); + mSkipRoutingControlHandler = new Handler(service.getServiceLooper()); } @Override @@ -184,7 +192,14 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.getHdmiCecNetwork().addCecSwitch( mService.getHdmiCecNetwork().getPhysicalAddress()); // TV is a CEC switch too. mTvInputs.clear(); + mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE); + mSkipRoutingControlHandler.removeCallbacks(mResetSkipRoutingControlRunnable); + if (mSkipRoutingControl) { + mSkipRoutingControlHandler.postDelayed(mResetSkipRoutingControlRunnable, + HdmiConfig.TIMEOUT_MS); + } + launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && reason != HdmiControlService.INITIATED_BY_BOOT_UP); resetSelectRequestBuffer(); diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java index c76ca2beda96..fba71fd0ff9e 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java @@ -114,7 +114,7 @@ final class AdditionalSubtypeUtils { * @param userId The user ID to be associated with. */ static void save(ArrayMap<String, List<InputMethodSubtype>> allSubtypes, - ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) { + InputMethodMap methodMap, @UserIdInt int userId) { final File inputMethodDir = getInputMethodDir(userId); if (allSubtypes.isEmpty()) { @@ -143,7 +143,7 @@ final class AdditionalSubtypeUtils { @VisibleForTesting static void saveToFile(ArrayMap<String, List<InputMethodSubtype>> allSubtypes, - ArrayMap<String, InputMethodInfo> methodMap, AtomicFile subtypesFile) { + InputMethodMap methodMap, AtomicFile subtypesFile) { // Safety net for the case that this function is called before methodMap is set. final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0; FileOutputStream fos = null; diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java index 4b85d099b600..62adb25954b4 100644 --- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java +++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java @@ -20,7 +20,6 @@ import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.util.ArrayMap; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -44,24 +43,23 @@ final class HardwareKeyboardShortcutController { return mUserId; } - HardwareKeyboardShortcutController( - @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) { + HardwareKeyboardShortcutController(@NonNull InputMethodMap methodMap, @UserIdInt int userId) { mUserId = userId; reset(methodMap); } @GuardedBy("ImfLock.class") - void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) { + void reset(@NonNull InputMethodMap methodMap) { mSubtypeHandles.clear(); - final InputMethodSettings settings = new InputMethodSettings(methodMap, mUserId); - final List<InputMethodInfo> inputMethods = settings.getEnabledInputMethodListLocked(); + final InputMethodSettings settings = InputMethodSettings.create(methodMap, mUserId); + final List<InputMethodInfo> inputMethods = settings.getEnabledInputMethodList(); for (int i = 0; i < inputMethods.size(); ++i) { final InputMethodInfo imi = inputMethods.get(i); if (!imi.shouldShowInInputMethodPicker()) { continue; } final List<InputMethodSubtype> subtypes = - settings.getEnabledInputMethodSubtypeListLocked(imi, true); + settings.getEnabledInputMethodSubtypeList(imi, true); if (subtypes.isEmpty()) { mSubtypeHandles.add(InputMethodSubtypeHandle.of(imi, null)); } else { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java index 542165d06a92..6339686629f5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java @@ -23,7 +23,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -204,7 +203,7 @@ final class InputMethodInfoUtils { */ @Nullable static InputMethodInfo chooseSystemVoiceIme( - @NonNull ArrayMap<String, InputMethodInfo> methodMap, + @NonNull InputMethodMap methodMap, @Nullable String systemSpeechRecognizerPackageName, @Nullable String currentDefaultVoiceImeId) { if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8448fc233ae2..beb68d3566c3 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -314,10 +314,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Nullable private VirtualDeviceManagerInternal mVdmInternal = null; - // All known input methods. - final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>(); - private final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>(); - // Mapping from deviceId to the device-specific imeId for that device. @GuardedBy("ImfLock.class") private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>(); @@ -330,7 +326,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController; /** - * Tracks how many times {@link #mMethodMap} was updated. + * Tracks how many times {@link #mSettings} was updated. */ @GuardedBy("ImfLock.class") private int mMethodMapUpdateCount = 0; @@ -472,7 +468,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") @Nullable InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) { - return mMethodMap.get(imeId); + return mSettings.getMethodMap().get(imeId); } /** @@ -1265,10 +1261,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } String curInputMethodId = mSettings.getSelectedInputMethod(); - final int numImes = mMethodList.size(); + final List<InputMethodInfo> methodList = mSettings.getMethodList(); + final int numImes = methodList.size(); if (curInputMethodId != null) { for (int i = 0; i < numImes; i++) { - InputMethodInfo imi = mMethodList.get(i); + InputMethodInfo imi = methodList.get(i); if (imi.getId().equals(curInputMethodId)) { for (String pkg : packages) { if (imi.getPackageName().equals(pkg)) { @@ -1339,7 +1336,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onPackageDataCleared(String packageName, int uid) { boolean changed = false; - for (InputMethodInfo imi : mMethodList) { + for (InputMethodInfo imi : mSettings.getMethodList()) { if (imi.getPackageName().equals(packageName)) { mAdditionalSubtypeMap.remove(imi.getId()); changed = true; @@ -1347,7 +1344,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (changed) { AdditionalSubtypeUtils.save( - mAdditionalSubtypeMap, mMethodMap, mSettings.getCurrentUserId()); + mAdditionalSubtypeMap, mSettings.getMethodMap(), + mSettings.getCurrentUserId()); mChangedPackages.add(packageName); } } @@ -1405,10 +1403,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub InputMethodInfo curIm = null; String curInputMethodId = mSettings.getSelectedInputMethod(); - final int numImes = mMethodList.size(); + final List<InputMethodInfo> methodList = mSettings.getMethodList(); + final int numImes = methodList.size(); if (curInputMethodId != null) { for (int i = 0; i < numImes; i++) { - InputMethodInfo imi = mMethodList.get(i); + InputMethodInfo imi = methodList.get(i); final String imiId = imi.getId(); if (imiId.equals(curInputMethodId)) { curIm = imi; @@ -1426,7 +1425,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + imi.getComponent()); mAdditionalSubtypeMap.remove(imi.getId()); AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, - mMethodMap, + mSettings.getMethodMap(), mSettings.getCurrentUserId()); } } @@ -1584,7 +1583,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (userId != currentUserId) { return; } - mSettings = new InputMethodSettings(mMethodMap, userId); + mSettings = InputMethodSettings.createEmptyMap(userId); if (mSystemReady) { // We need to rebuild IMEs. buildInputMethodListLocked(false /* resetDefaultEnabledIme */); @@ -1658,14 +1657,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mLastSwitchUserId = userId; // mSettings should be created before buildInputMethodListLocked - mSettings = new InputMethodSettings(mMethodMap, userId); + mSettings = InputMethodSettings.createEmptyMap(userId); AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); mSwitchingController = - InputMethodSubtypeSwitchingController.createInstanceLocked(context, mMethodMap, - userId); + InputMethodSubtypeSwitchingController.createInstanceLocked(context, + mSettings.getMethodMap(), userId); mHardwareKeyboardShortcutController = - new HardwareKeyboardShortcutController(mMethodMap, userId); + new HardwareKeyboardShortcutController(mSettings.getMethodMap(), + mSettings.getCurrentUserId()); mMenuController = new InputMethodMenuController(this); mBindingController = bindingControllerForTesting != null @@ -1723,11 +1723,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private void resetDefaultImeLocked(Context context) { // Do not reset the default (current) IME when it is a 3rd-party IME String selectedMethodId = getSelectedMethodIdLocked(); - if (selectedMethodId != null && !mMethodMap.get(selectedMethodId).isSystem()) { + if (selectedMethodId != null + && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) { return; } final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes( - context, mSettings.getEnabledInputMethodListLocked()); + context, mSettings.getEnabledInputMethodList()); if (suitableImes.isEmpty()) { Slog.i(TAG, "No default found"); return; @@ -1791,7 +1792,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // ContentObserver should be registered again when the user is changed mSettingsObserver.registerContentObserverLocked(newUserId); - mSettings = new InputMethodSettings(mMethodMap, newUserId); + mSettings = InputMethodSettings.createEmptyMap(newUserId); // Additional subtypes should be reset when the user is changed AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId); final String defaultImiId = mSettings.getSelectedInputMethod(); @@ -1822,7 +1823,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (initialUserSwitch) { InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, newUserId), - mSettings.getEnabledInputMethodListLocked()); + mSettings.getEnabledInputMethodList()); } if (DEBUG) { @@ -1899,7 +1900,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub updateFromSettingsLocked(true); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), - mSettings.getEnabledInputMethodListLocked()); + mSettings.getEnabledInputMethodList()); } } } @@ -2001,9 +2002,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList. //TODO(b/210039666): use cache. - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); - final InputMethodInfo imi = methodMap.get(settings.getSelectedInputMethod()); + final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodInfo imi = settings.getMethodMap().get( + settings.getSelectedInputMethod()); return imi != null && imi.supportsStylusHandwriting(); } } @@ -2023,23 +2024,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId, @DirectBootAwareness int directBootAwareness, int callingUid) { - final ArrayList<InputMethodInfo> methodList; final InputMethodSettings settings; if (userId == mSettings.getCurrentUserId() && directBootAwareness == DirectBootAwareness.AUTO) { - // Create a copy. - methodList = new ArrayList<>(mMethodList); settings = mSettings; } else { - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - methodList = new ArrayList<>(); final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); - queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, - methodList, directBootAwareness); - settings = new InputMethodSettings(methodMap, userId); + settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, + directBootAwareness); } + // Create a copy. + final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList()); // filter caller's access to input methods methodList.removeIf(imi -> !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)); @@ -2052,12 +2049,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final ArrayList<InputMethodInfo> methodList; final InputMethodSettings settings; if (userId == mSettings.getCurrentUserId()) { - methodList = mSettings.getEnabledInputMethodListLocked(); + methodList = mSettings.getEnabledInputMethodList(); settings = mSettings; } else { - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - settings = new InputMethodSettings(methodMap, userId); - methodList = settings.getEnabledInputMethodListLocked(); + settings = queryMethodMapForUser(userId); + methodList = settings.getEnabledInputMethodList(); } // filter caller's access to input methods methodList.removeIf(imi -> @@ -2120,27 +2116,26 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final InputMethodInfo imi; String selectedMethodId = getSelectedMethodIdLocked(); if (imiId == null && selectedMethodId != null) { - imi = mMethodMap.get(selectedMethodId); + imi = mSettings.getMethodMap().get(selectedMethodId); } else { - imi = mMethodMap.get(imiId); + imi = mSettings.getMethodMap().get(imiId); } if (imi == null || !canCallerAccessInputMethod( imi.getPackageName(), callingUid, userId, mSettings)) { return Collections.emptyList(); } - return mSettings.getEnabledInputMethodSubtypeListLocked( + return mSettings.getEnabledInputMethodSubtypeList( imi, allowsImplicitlyEnabledSubtypes); } - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - final InputMethodInfo imi = methodMap.get(imiId); + final InputMethodSettings settings = queryMethodMapForUser(userId); + final InputMethodInfo imi = settings.getMethodMap().get(imiId); if (imi == null) { return Collections.emptyList(); } - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) { return Collections.emptyList(); } - return settings.getEnabledInputMethodSubtypeListLocked( + return settings.getEnabledInputMethodSubtypeList( imi, allowsImplicitlyEnabledSubtypes); } @@ -2343,7 +2338,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } String curId = getCurIdLocked(); - final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId); + final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = @@ -2559,7 +2554,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId); if (Objects.equals(deviceMethodId, currentMethodId)) { return currentMethodId; - } else if (!mMethodMap.containsKey(deviceMethodId)) { + } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) { if (DEBUG) { Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme + " because its custom input method is not available: " + deviceMethodId); @@ -2601,7 +2596,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) { return false; } - final InputMethodInfo imi = mMethodMap.get(selectedMethodId); + final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId); if (imi == null) { return false; } @@ -3023,7 +3018,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } - List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilterLocked( + List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter( InputMethodInfo::shouldShowInInputMethodPicker); final int numImes = imes.size(); if (numImes > 2) return true; @@ -3035,7 +3030,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub for (int i = 0; i < numImes; ++i) { final InputMethodInfo imi = imes.get(i); final List<InputMethodSubtype> subtypes = - mSettings.getEnabledInputMethodSubtypeListLocked(imi, true); + mSettings.getEnabledInputMethodSubtypeList(imi, true); final int subtypeCount = subtypes.size(); if (subtypeCount == 0) { ++nonAuxCount; @@ -3187,7 +3182,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext, mSettings.getCurrentUserId()); - List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); + List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList(); for (int i = 0; i < enabled.size(); i++) { // We allow the user to select "disabled until used" apps, so if they // are enabling one of those here we now need to make it enabled. @@ -3234,17 +3229,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // TODO: Instantiate mSwitchingController for each user. if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) { - mSwitchingController.resetCircularListLocked(mMethodMap); + mSwitchingController.resetCircularListLocked(mSettings.getMethodMap()); } else { mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, mMethodMap, mSettings.getCurrentUserId()); + mContext, mSettings.getMethodMap(), mSettings.getCurrentUserId()); } // TODO: Instantiate mHardwareKeyboardShortcutController for each user. if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) { - mHardwareKeyboardShortcutController.reset(mMethodMap); + mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap()); } else { mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - mMethodMap, mSettings.getCurrentUserId()); + mSettings.getMethodMap(), mSettings.getCurrentUserId()); } sendOnNavButtonFlagsChangedLocked(); } @@ -3268,7 +3263,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void setInputMethodLocked(String id, int subtypeId, int deviceId) { - InputMethodInfo info = mMethodMap.get(id); + InputMethodInfo info = mSettings.getMethodMap().get(id); if (info == null) { throw getExceptionForUnknownImeId(id); } @@ -4017,7 +4012,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!calledWithValidTokenLocked(token)) { return; } - final InputMethodInfo imi = mMethodMap.get(id); + final InputMethodInfo imi = mSettings.getMethodMap().get(id); if (imi == null || !canCallerAccessInputMethod( imi.getPackageName(), callingUid, userId, mSettings)) { throw getExceptionForUnknownImeId(id); @@ -4035,7 +4030,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!calledWithValidTokenLocked(token)) { return; } - final InputMethodInfo imi = mMethodMap.get(id); + final InputMethodInfo imi = mSettings.getMethodMap().get(id); if (imi == null || !canCallerAccessInputMethod( imi.getPackageName(), callingUid, userId, mSettings)) { throw getExceptionForUnknownImeId(id); @@ -4055,10 +4050,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!calledWithValidTokenLocked(token)) { return false; } - final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); + final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype(); final InputMethodInfo lastImi; if (lastIme != null) { - lastImi = mMethodMap.get(lastIme.first); + lastImi = mSettings.getMethodMap().get(lastIme.first); } else { lastImi = null; } @@ -4082,7 +4077,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // This is a safety net. If the currentSubtype can't be added to the history // and the framework couldn't find the last ime, we will make the last ime be // the most applicable enabled keyboard subtype of the system imes. - final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); + final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList(); if (enabled != null) { final int enabledCount = enabled.size(); final String locale; @@ -4097,7 +4092,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final InputMethodInfo imi = enabled.get(i); if (imi.getSubtypeCount() > 0 && imi.isSystem()) { InputMethodSubtype keyboardSubtype = - SubtypeUtils.findLastResortApplicableSubtypeLocked( + SubtypeUtils.findLastResortApplicableSubtype( SubtypeUtils.getSubtypes(imi), SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true); if (keyboardSubtype != null) { @@ -4139,7 +4134,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) { final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - onlyCurrentIme, mMethodMap.get(getSelectedMethodIdLocked()), mCurrentSubtype); + onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()), + mCurrentSubtype); if (nextSubtype == null) { return false; } @@ -4155,8 +4151,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( - false /* onlyCurrentIme */, mMethodMap.get(getSelectedMethodIdLocked()), - mCurrentSubtype); + false /* onlyCurrentIme */, + mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype); return nextSubtype != null; } } @@ -4169,12 +4165,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { if (mSettings.getCurrentUserId() == userId) { - return mSettings.getLastInputMethodSubtypeLocked(); + return mSettings.getLastInputMethodSubtype(); } - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); - return settings.getLastInputMethodSubtypeLocked(); + final InputMethodSettings settings = queryMethodMapForUser(userId); + return settings.getLastInputMethodSubtype(); } } @@ -4218,14 +4213,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return; } - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); - queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, - methodList, DirectBootAwareness.AUTO); - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); + final InputMethodSettings settings = queryInputMethodServicesInternal(mContext, userId, + additionalSubtypeMap, DirectBootAwareness.AUTO); settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid); } @@ -4253,8 +4245,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { final boolean currentUser = (mSettings.getCurrentUserId() == userId); final InputMethodSettings settings = currentUser - ? mSettings - : new InputMethodSettings(queryMethodMapForUser(userId), userId); + ? mSettings : queryMethodMapForUser(userId); if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) { return; } @@ -4626,7 +4617,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (mSettings.getCurrentUserId() != mSwitchingController.getUserId()) { return; } - final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked()); + final InputMethodInfo imi = + mSettings.getMethodMap().get(getSelectedMethodIdLocked()); if (imi != null) { mSwitchingController.onUserActionLocked(imi, mCurrentSubtype); } @@ -4684,8 +4676,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return; } else { // Called with current IME's token. - if (mMethodMap.get(id) != null - && mSettings.getEnabledInputMethodListWithFilterLocked( + if (mSettings.getMethodMap().get(id) != null + && mSettings.getEnabledInputMethodListWithFilter( (info) -> info.getId().equals(id)).isEmpty()) { throw new IllegalStateException("Requested IME is not enabled: " + id); } @@ -4866,7 +4858,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController .getSortedInputMethodAndSubtypeList( showAuxSubtypes, isScreenLocked, true /* forImeMenu */, - mContext, mMethodMap, mSettings.getCurrentUserId()); + mContext, mSettings.getMethodMap(), + mSettings.getCurrentUserId()); mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId, lastInputMethodId, lastInputMethodSubtypeId, imList); } @@ -5050,7 +5043,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean chooseNewDefaultIMELocked() { final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME( - mSettings.getEnabledInputMethodListLocked()); + mSettings.getEnabledInputMethodList()); if (imi != null) { if (DEBUG) { Slog.d(TAG, "New default IME was selected: " + imi.getId()); @@ -5062,17 +5055,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } - static void queryInputMethodServicesInternal(Context context, + @NonNull + static InputMethodSettings queryInputMethodServicesInternal(Context context, @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, - ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, @DirectBootAwareness int directBootAwareness) { final Context userAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); - methodList.clear(); - methodMap.clear(); - final int directBootAwarenessFlags; switch (directBootAwareness) { case DirectBootAwareness.ANY: @@ -5095,24 +5085,23 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new Intent(InputMethod.SERVICE_INTERFACE), PackageManager.ResolveInfoFlags.of(flags)); - methodList.ensureCapacity(services.size()); - methodMap.ensureCapacity(services.size()); - // Note: This is a temporary solution for Bug 261723412. If there is any better solution, // we should remove this data dependency. final List<String> enabledInputMethodList = InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId); - filterInputMethodServices(additionalSubtypeMap, methodMap, methodList, - enabledInputMethodList, userAwareContext, services); + final InputMethodMap methodMap = filterInputMethodServices( + additionalSubtypeMap, enabledInputMethodList, userAwareContext, services); + return InputMethodSettings.create(methodMap, userId); } - static void filterInputMethodServices( + @NonNull + static InputMethodMap filterInputMethodServices( ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, - ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, List<String> enabledInputMethodList, Context userAwareContext, List<ResolveInfo> services) { final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>(); + final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(services.size()); for (int i = 0; i < services.size(); ++i) { ResolveInfo ri = services.get(i); @@ -5141,7 +5130,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub imiPackageCount.put(packageName, 1 + imiPackageCount.getOrDefault(packageName, 0)); - methodList.add(imi); methodMap.put(imi.getId(), imi); if (DEBUG) { Slog.d(TAG, "Found an input method " + imi); @@ -5153,6 +5141,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Slog.wtf(TAG, "Unable to load input method " + imeId, e); } } + return InputMethodMap.of(methodMap); } @GuardedBy("ImfLock.class") @@ -5168,8 +5157,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mMethodMapUpdateCount++; mMyPackageMonitor.clearKnownImePackageNamesLocked(); - queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(), - mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO); + mSettings = queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(), + mAdditionalSubtypeMap, DirectBootAwareness.AUTO); // Construct the set of possible IME packages for onPackageChanged() to avoid false // negatives when the package state remains to be the same but only the component state is @@ -5196,11 +5185,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!resetDefaultEnabledIme) { boolean enabledImeFound = false; boolean enabledNonAuxImeFound = false; - final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked(); + final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList(); final int numImes = enabledImes.size(); for (int i = 0; i < numImes; ++i) { final InputMethodInfo imi = enabledImes.get(i); - if (mMethodList.contains(imi)) { + if (mSettings.getMethodMap().containsKey(imi.getId())) { enabledImeFound = true; if (!imi.isAuxiliaryIme()) { enabledNonAuxImeFound = true; @@ -5224,7 +5213,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) { final ArrayList<InputMethodInfo> defaultEnabledIme = - InputMethodInfoUtils.getDefaultEnabledImes(mContext, mMethodList, + InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(), reenableMinimumNonAuxSystemImes); final int numImes = defaultEnabledIme.size(); for (int i = 0; i < numImes; ++i) { @@ -5238,7 +5227,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final String defaultImiId = mSettings.getSelectedInputMethod(); if (!TextUtils.isEmpty(defaultImiId)) { - if (!mMethodMap.containsKey(defaultImiId)) { + if (!mSettings.getMethodMap().containsKey(defaultImiId)) { Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); if (chooseNewDefaultIMELocked()) { updateInputMethodsFromSettingsLocked(true); @@ -5253,23 +5242,23 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // TODO: Instantiate mSwitchingController for each user. if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) { - mSwitchingController.resetCircularListLocked(mMethodMap); + mSwitchingController.resetCircularListLocked(mSettings.getMethodMap()); } else { mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, mMethodMap, mSettings.getCurrentUserId()); + mContext, mSettings.getMethodMap(), mSettings.getCurrentUserId()); } // TODO: Instantiate mHardwareKeyboardShortcutController for each user. if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) { - mHardwareKeyboardShortcutController.reset(mMethodMap); + mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap()); } else { mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - mMethodMap, mSettings.getCurrentUserId()); + mSettings.getMethodMap(), mSettings.getCurrentUserId()); } sendOnNavButtonFlagsChangedLocked(); // Notify InputMethodListListeners of the new installed InputMethods. - final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList); + final List<InputMethodInfo> inputMethodList = mSettings.getMethodList(); mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED, mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget(); } @@ -5290,7 +5279,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer); final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod(); final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme( - mMethodMap, systemSpeechRecognizer, currentDefaultVoiceImeId); + mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId); if (newSystemVoiceIme == null) { if (DEBUG) { Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked," @@ -5341,9 +5330,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } else { final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings - .getEnabledInputMethodsAndSubtypeListLocked(); + .getEnabledInputMethodsAndSubtypeList(); StringBuilder builder = new StringBuilder(); - if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( + if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId( builder, enabledInputMethodsList, id)) { if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) { // Disabled input method is currently selected, switch to another one. @@ -5357,7 +5346,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // new default one but only update the settings. InputMethodInfo newDefaultIme = InputMethodInfoUtils.getMostApplicableDefaultIME( - mSettings.getEnabledInputMethodListLocked()); + mSettings.getEnabledInputMethodList()); mSettings.putSelectedDefaultDeviceInputMethod( newDefaultIme == null ? "" : newDefaultIme.getId()); } @@ -5402,11 +5391,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { - InputMethodInfo imi = mMethodMap.get(newDefaultIme); + InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme); int lastSubtypeId = NOT_A_SUBTYPE_ID; // newDefaultIme is empty when there is no candidate for the selected IME. if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { - String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); + String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme); if (subtypeHashCode != null) { try { lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi, @@ -5437,8 +5426,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return getCurrentInputMethodSubtypeLocked(); } - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); + final InputMethodSettings settings = queryMethodMapForUser(userId); return settings.getCurrentInputMethodSubtypeForNonCurrentUsers(); } } @@ -5460,7 +5448,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return null; } final boolean subtypeIsSelected = mSettings.isSubtypeSelected(); - final InputMethodInfo imi = mMethodMap.get(selectedMethodId); + final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId); if (imi == null || imi.getSubtypeCount() == 0) { return null; } @@ -5472,7 +5460,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // the most applicable subtype from explicitly or implicitly enabled // subtypes. List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = - mSettings.getEnabledInputMethodSubtypeListLocked(imi, true); + mSettings.getEnabledInputMethodSubtypeList(imi, true); // If there is only one explicitly or implicitly enabled subtype, // just returns it. if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { @@ -5480,11 +5468,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { final String locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()) .get(0).toString(); - mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( + mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype( explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true); if (mCurrentSubtype == null) { - mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( + mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype( explicitlyOrImplicitlyEnabledSubtypes, null, locale, true); } } @@ -5501,46 +5489,42 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ @GuardedBy("ImfLock.class") private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) { + final InputMethodSettings settings; if (userId == mSettings.getCurrentUserId()) { - return mMethodMap.get(mSettings.getSelectedInputMethod()); + settings = mSettings; + } else { + final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = + new ArrayMap<>(); + AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); + settings = queryInputMethodServicesInternal(mContext, userId, + additionalSubtypeMap, DirectBootAwareness.AUTO); } - - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); - final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>(); - AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); - queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, - methodList, DirectBootAwareness.AUTO); - InputMethodSettings settings = new InputMethodSettings(methodMap, userId); - return methodMap.get(settings.getSelectedInputMethod()); + return settings.getMethodMap().get(settings.getSelectedInputMethod()); } - private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) { - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); + private InputMethodSettings queryMethodMapForUser(@UserIdInt int userId) { final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); - queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList, DirectBootAwareness.AUTO); - return methodMap; + return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, + DirectBootAwareness.AUTO); } @GuardedBy("ImfLock.class") private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) { if (userId == mSettings.getCurrentUserId()) { - if (!mMethodMap.containsKey(imeId) - || !mSettings.getEnabledInputMethodListLocked() - .contains(mMethodMap.get(imeId))) { + if (!mSettings.getMethodMap().containsKey(imeId) + || !mSettings.getEnabledInputMethodList() + .contains(mSettings.getMethodMap().get(imeId))) { return false; // IME is not found or not enabled. } setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); return true; } - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); - if (!methodMap.containsKey(imeId) - || !settings.getEnabledInputMethodListLocked().contains(methodMap.get(imeId))) { + final InputMethodSettings settings = queryMethodMapForUser(userId); + if (!settings.getMethodMap().containsKey(imeId) + || !settings.getEnabledInputMethodList().contains( + settings.getMethodMap().get(imeId))) { return false; // IME is not found or not enabled. } settings.putSelectedInputMethod(imeId); @@ -5578,7 +5562,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private void switchKeyboardLayoutLocked(int direction) { - final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked()); + final InputMethodInfo currentImi = mSettings.getMethodMap().get( + getSelectedMethodIdLocked()); if (currentImi == null) { return; } @@ -5590,7 +5575,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (nextSubtypeHandle == null) { return; } - final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId()); + final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId()); if (nextImi == null) { return; } @@ -5670,15 +5655,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) { synchronized (ImfLock.class) { if (userId == mSettings.getCurrentUserId()) { - if (!mMethodMap.containsKey(imeId)) { + if (!mSettings.getMethodMap().containsKey(imeId)) { return false; // IME is not found. } setInputMethodEnabledLocked(imeId, enabled); return true; } - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); - if (!methodMap.containsKey(imeId)) { + final InputMethodSettings settings = queryMethodMapForUser(userId); + if (!settings.getMethodMap().containsKey(imeId)) { return false; // IME is not found. } if (enabled) { @@ -5689,9 +5673,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub settings.putEnabledInputMethodsStr(newEnabledImeIdsStr); } } else { - settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( + settings.buildAndPutEnabledInputMethodsStrRemovingId( new StringBuilder(), - settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId); + settings.getEnabledInputMethodsAndSubtypeList(), imeId); } return true; } @@ -5997,10 +5981,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { p.println("Current Input Method Manager state:"); - int numImes = mMethodList.size(); + final List<InputMethodInfo> methodList = mSettings.getMethodList(); + int numImes = methodList.size(); p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount); for (int i = 0; i < numImes; i++) { - InputMethodInfo info = mMethodList.get(i); + InputMethodInfo info = methodList.get(i); p.println(" InputMethod #" + i + ":"); info.dump(p, " "); } @@ -6047,7 +6032,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub p.println(" mSwitchingController:"); mSwitchingController.dump(p); p.println(" mSettings:"); - mSettings.dumpLocked(p, " "); + mSettings.dump(p, " "); p.println(" mStartInputHistory:"); mStartInputHistory.dump(pw, " "); @@ -6416,16 +6401,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub boolean failedToEnableUnknownIme = false; boolean previouslyEnabled = false; if (userId == mSettings.getCurrentUserId()) { - if (enabled && !mMethodMap.containsKey(imeId)) { + if (enabled && !mSettings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; } else { previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled); } } else { - final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); + final InputMethodSettings settings = queryMethodMapForUser(userId); if (enabled) { - if (!methodMap.containsKey(imeId)) { + if (!settings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; } else { final String enabledImeIdsStr = settings.getEnabledInputMethodsStr(); @@ -6438,9 +6422,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } else { previouslyEnabled = - settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( + settings.buildAndPutEnabledInputMethodsStrRemovingId( new StringBuilder(), - settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId); + settings.getEnabledInputMethodsAndSubtypeList(), imeId); } } if (failedToEnableUnknownIme) { @@ -6537,9 +6521,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mBindingController.unbindCurrentMethod(); // Enable default IMEs, disable others - var toDisable = mSettings.getEnabledInputMethodListLocked(); + var toDisable = mSettings.getEnabledInputMethodList(); var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes( - mContext, mMethodList); + mContext, mSettings.getMethodList()); toDisable.removeAll(defaultEnabled); for (InputMethodInfo info : toDisable) { setInputMethodEnabledLocked(info.getId(), false); @@ -6554,22 +6538,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, mSettings.getCurrentUserId()), - mSettings.getEnabledInputMethodListLocked()); + mSettings.getEnabledInputMethodList()); nextIme = mSettings.getSelectedInputMethod(); - nextEnabledImes = mSettings.getEnabledInputMethodListLocked(); + nextEnabledImes = mSettings.getEnabledInputMethodList(); } else { - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); - queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList, DirectBootAwareness.AUTO); - final InputMethodSettings settings = new InputMethodSettings( - methodMap, userId); + final InputMethodSettings settings = queryInputMethodServicesInternal( + mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO); nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext, - methodList); + settings.getMethodList()); nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME( nextEnabledImes).getId(); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java new file mode 100644 index 000000000000..a8e5e2ef4f72 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.ArrayMap; +import android.view.inputmethod.InputMethodInfo; + +import java.util.List; + +/** + * A map from IME ID to {@link InputMethodInfo}, which is guaranteed to be immutable thus + * thread-safe. + */ +final class InputMethodMap { + private static final ArrayMap<String, InputMethodInfo> EMPTY_MAP = + new ArrayMap<>(); + + private final ArrayMap<String, InputMethodInfo> mMap; + + static InputMethodMap emptyMap() { + return new InputMethodMap(EMPTY_MAP); + } + + static InputMethodMap of(@NonNull ArrayMap<String, InputMethodInfo> map) { + return new InputMethodMap(map); + } + + private InputMethodMap(@NonNull ArrayMap<String, InputMethodInfo> map) { + mMap = map.isEmpty() ? EMPTY_MAP : new ArrayMap<>(map); + } + + @AnyThread + @Nullable + InputMethodInfo get(@Nullable String imeId) { + return mMap.get(imeId); + } + + @AnyThread + @NonNull + List<InputMethodInfo> values() { + return List.copyOf(mMap.values()); + } + + @AnyThread + @Nullable + InputMethodInfo valueAt(int index) { + return mMap.valueAt(index); + } + + @AnyThread + boolean containsKey(@Nullable String imeId) { + return mMap.containsKey(imeId); + } + + @AnyThread + @IntRange(from = 0) + int size() { + return mMap.size(); + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java index 9ddb428018c0..abd7688b60cc 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java @@ -16,6 +16,7 @@ package com.android.server.inputmethod; +import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -58,7 +59,9 @@ final class InputMethodSettings { private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = InputMethodUtils.INPUT_METHOD_SUBTYPE_SEPARATOR; - private final ArrayMap<String, InputMethodInfo> mMethodMap; + private final InputMethodMap mMethodMap; + private final List<InputMethodInfo> mMethodList; + @UserIdInt private final int mCurrentUserId; @@ -73,8 +76,17 @@ final class InputMethodSettings { } } - InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) { + static InputMethodSettings createEmptyMap(@UserIdInt int userId) { + return new InputMethodSettings(InputMethodMap.emptyMap(), userId); + } + + static InputMethodSettings create(InputMethodMap methodMap, @UserIdInt int userId) { + return new InputMethodSettings(methodMap, userId); + } + + private InputMethodSettings(InputMethodMap methodMap, @UserIdInt int userId) { mMethodMap = methodMap; + mMethodList = methodMap.values(); mCurrentUserId = userId; String ime = getSelectedInputMethod(); String defaultDeviceIme = getSelectedDefaultDeviceInputMethod(); @@ -84,6 +96,18 @@ final class InputMethodSettings { } } + @AnyThread + @NonNull + InputMethodMap getMethodMap() { + return mMethodMap; + } + + @AnyThread + @NonNull + List<InputMethodInfo> getMethodList() { + return mMethodList; + } + private void putString(@NonNull String key, @Nullable String str) { SecureSettingsWrapper.putString(key, str, mCurrentUserId); } @@ -101,32 +125,32 @@ final class InputMethodSettings { return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId); } - ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { - return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */); + ArrayList<InputMethodInfo> getEnabledInputMethodList() { + return getEnabledInputMethodListWithFilter(null /* matchingCondition */); } @NonNull - ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked( + ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilter( @Nullable Predicate<InputMethodInfo> matchingCondition) { - return createEnabledInputMethodListLocked( - getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition); + return createEnabledInputMethodList( + getEnabledInputMethodsAndSubtypeList(), matchingCondition); } - List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( + List<InputMethodSubtype> getEnabledInputMethodSubtypeList( InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) { List<InputMethodSubtype> enabledSubtypes = - getEnabledInputMethodSubtypeListLocked(imi); + getEnabledInputMethodSubtypeList(imi); if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) { - enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypes( SystemLocaleWrapper.get(mCurrentUserId), imi); } return InputMethodSubtype.sort(imi, enabledSubtypes); } - List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) { - final List<Pair<String, ArrayList<String>>> imsList = - getEnabledInputMethodsAndSubtypeListLocked(); - final List<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); + List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi) { + List<Pair<String, ArrayList<String>>> imsList = + getEnabledInputMethodsAndSubtypeList(); + ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); if (imi != null) { for (int i = 0; i < imsList.size(); ++i) { final Pair<String, ArrayList<String>> imsPair = imsList.get(i); @@ -149,7 +173,7 @@ final class InputMethodSettings { return enabledSubtypes; } - List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { + List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeList() { final String enabledInputMethodsStr = getEnabledInputMethodsStr(); final TextUtils.SimpleStringSplitter inputMethodSplitter = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR); @@ -181,7 +205,7 @@ final class InputMethodSettings { * * @return the specified id was removed or not. */ - boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( + boolean buildAndPutEnabledInputMethodsStrRemovingId( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { boolean isRemoved = false; boolean needsAppendSeparator = false; @@ -209,7 +233,7 @@ final class InputMethodSettings { return isRemoved; } - private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked( + private ArrayList<InputMethodInfo> createEnabledInputMethodList( List<Pair<String, ArrayList<String>>> imsList, Predicate<InputMethodInfo> matchingCondition) { final ArrayList<InputMethodInfo> res = new ArrayList<>(); @@ -271,7 +295,7 @@ final class InputMethodSettings { } private void addSubtypeToHistory(String imeId, String subtypeId) { - final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); + final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistory(); for (int i = 0; i < subtypeHistory.size(); ++i) { final Pair<String, String> ime = subtypeHistory.get(i); if (ime.first.equals(imeId)) { @@ -303,14 +327,14 @@ final class InputMethodSettings { } } - Pair<String, String> getLastInputMethodAndSubtypeLocked() { + Pair<String, String> getLastInputMethodAndSubtype() { // Gets the first one from the history - return getLastSubtypeForInputMethodLockedInternal(null); + return getLastSubtypeForInputMethodInternal(null); } @Nullable - InputMethodSubtype getLastInputMethodSubtypeLocked() { - final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked(); + InputMethodSubtype getLastInputMethodSubtype() { + final Pair<String, String> lastIme = getLastInputMethodAndSubtype(); // TODO: Handle the case of the last IME with no subtypes if (lastIme == null || TextUtils.isEmpty(lastIme.first) || TextUtils.isEmpty(lastIme.second)) { @@ -331,8 +355,8 @@ final class InputMethodSettings { } } - String getLastSubtypeForInputMethodLocked(String imeId) { - Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); + String getLastSubtypeForInputMethod(String imeId) { + Pair<String, String> ime = getLastSubtypeForInputMethodInternal(imeId); if (ime != null) { return ime.second; } else { @@ -340,10 +364,10 @@ final class InputMethodSettings { } } - private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { + private Pair<String, String> getLastSubtypeForInputMethodInternal(String imeId) { final List<Pair<String, ArrayList<String>>> enabledImes = - getEnabledInputMethodsAndSubtypeListLocked(); - final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); + getEnabledInputMethodsAndSubtypeList(); + final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistory(); for (int i = 0; i < subtypeHistory.size(); ++i) { final Pair<String, String> imeAndSubtype = subtypeHistory.get(i); final String imeInTheHistory = imeAndSubtype.first; @@ -351,7 +375,7 @@ final class InputMethodSettings { if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { final String subtypeInTheHistory = imeAndSubtype.second; final String subtypeHashCode = - getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( + getEnabledSubtypeHashCodeForInputMethodAndSubtype( enabledImes, imeInTheHistory, subtypeInTheHistory); if (!TextUtils.isEmpty(subtypeHashCode)) { if (DEBUG) { @@ -368,7 +392,7 @@ final class InputMethodSettings { return null; } - private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, + private String getEnabledSubtypeHashCodeForInputMethodAndSubtype(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId); for (int i = 0; i < enabledImes.size(); ++i) { @@ -383,7 +407,7 @@ final class InputMethodSettings { // are enabled implicitly, so needs to treat them to be enabled. if (imi != null && imi.getSubtypeCount() > 0) { List<InputMethodSubtype> implicitlyEnabledSubtypes = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList, + SubtypeUtils.getImplicitlyApplicableSubtypes(localeList, imi); final int numSubtypes = implicitlyEnabledSubtypes.size(); for (int j = 0; j < numSubtypes; ++j) { @@ -420,7 +444,7 @@ final class InputMethodSettings { return null; } - private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { + private List<Pair<String, String>> loadInputMethodAndSubtypeHistory() { ArrayList<Pair<String, String>> imsList = new ArrayList<>(); final String subtypeHistoryStr = getSubtypeHistoryStr(); if (TextUtils.isEmpty(subtypeHistoryStr)) { @@ -583,7 +607,7 @@ final class InputMethodSettings { // If there are no selected subtypes, the framework will try to find the most applicable // subtype from explicitly or implicitly enabled subtypes. final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = - getEnabledInputMethodSubtypeListLocked(imi, true); + getEnabledInputMethodSubtypeList(imi, true); // If there is only one explicitly or implicitly enabled subtype, just returns it. if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) { return null; @@ -592,13 +616,13 @@ final class InputMethodSettings { return explicitlyOrImplicitlyEnabledSubtypes.get(0); } final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString(); - final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( + final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtype( explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true); if (subtype != null) { return subtype; } - return SubtypeUtils.findLastResortApplicableSubtypeLocked( + return SubtypeUtils.findLastResortApplicableSubtype( explicitlyOrImplicitlyEnabledSubtypes, null, locale, true); } @@ -690,7 +714,7 @@ final class InputMethodSettings { return sb.toString(); } - void dumpLocked(final Printer pw, final String prefix) { + void dump(final Printer pw, final String prefix) { pw.println(prefix + "mCurrentUserId=" + mCurrentUserId); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 834ba20b84fd..1379d166e805 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -23,7 +23,6 @@ import android.annotation.UserIdInt; import android.content.Context; import android.os.UserHandle; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.Printer; import android.util.Slog; @@ -158,15 +157,15 @@ final class InputMethodSubtypeSwitchingController { static List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList( boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu, - @NonNull Context context, @NonNull ArrayMap<String, InputMethodInfo> methodMap, + @NonNull Context context, @NonNull InputMethodMap methodMap, @UserIdInt int userId) { final Context userAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag(); - final InputMethodSettings settings = new InputMethodSettings(methodMap, userId); + final InputMethodSettings settings = InputMethodSettings.create(methodMap, userId); - final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked(); + final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList(); if (imis.isEmpty()) { return new ArrayList<>(); } @@ -184,7 +183,7 @@ final class InputMethodSubtypeSwitchingController { continue; } final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = - settings.getEnabledInputMethodSubtypeListLocked(imi, true); + settings.getEnabledInputMethodSubtypeList(imi, true); final ArraySet<String> enabledSubtypeSet = new ArraySet<>(); for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) { enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); @@ -479,7 +478,7 @@ final class InputMethodSubtypeSwitchingController { private ControllerImpl mController; private InputMethodSubtypeSwitchingController(@NonNull Context context, - @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) { + @NonNull InputMethodMap methodMap, @UserIdInt int userId) { mContext = context; mUserId = userId; mController = ControllerImpl.createFrom(null, @@ -491,7 +490,7 @@ final class InputMethodSubtypeSwitchingController { @NonNull public static InputMethodSubtypeSwitchingController createInstanceLocked( @NonNull Context context, - @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) { + @NonNull InputMethodMap methodMap, @UserIdInt int userId) { return new InputMethodSubtypeSwitchingController(context, methodMap, userId); } @@ -511,8 +510,7 @@ final class InputMethodSubtypeSwitchingController { mController.onUserActionLocked(imi, subtype); } - public void resetCircularListLocked( - @NonNull ArrayMap<String, InputMethodInfo> methodMap) { + public void resetCircularListLocked(@NonNull InputMethodMap methodMap) { mController = ControllerImpl.createFrom(mController, getSortedInputMethodAndSubtypeList( false /* includeAuxiliarySubtypes */, false /* isScreenLocked */, diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java index 95df99855dcf..3d5c867768ac 100644 --- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java @@ -26,7 +26,6 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; @@ -52,7 +51,7 @@ final class SubtypeUtils { "EnabledWhenDefaultIsNotAsciiCapable"; // A temporary workaround for the performance concerns in - // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo). + // #getImplicitlyApplicableSubtypes(Resources, InputMethodInfo). // TODO: Optimize all the critical paths including this one. // TODO(b/235661780): Make the cache supports multi-users. private static final Object sCacheLock = new Object(); @@ -121,9 +120,8 @@ final class SubtypeUtils { private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale = source -> source != null ? source.getLocaleObject() : null; - @VisibleForTesting @NonNull - static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( + static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypes( @NonNull LocaleList systemLocales, InputMethodInfo imi) { synchronized (sCacheLock) { // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because @@ -133,11 +131,11 @@ final class SubtypeUtils { } } - // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl(). - // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive + // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesImpl(). + // TODO: Refactor getImplicitlyApplicableSubtypesImpl() so that it can receive // LocaleList rather than Resource. final ArrayList<InputMethodSubtype> result = - getImplicitlyApplicableSubtypesLockedImpl(systemLocales, imi); + getImplicitlyApplicableSubtypesImpl(systemLocales, imi); synchronized (sCacheLock) { // Both LocaleList and InputMethodInfo are immutable. No need to copy them here. sCachedSystemLocales = systemLocales; @@ -147,7 +145,7 @@ final class SubtypeUtils { return result; } - private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl( + private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesImpl( @NonNull LocaleList systemLocales, InputMethodInfo imi) { final List<InputMethodSubtype> subtypes = getSubtypes(imi); final String systemLocale = systemLocales.get(0).toString(); @@ -215,7 +213,7 @@ final class SubtypeUtils { } if (applicableSubtypes.isEmpty()) { - InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( + InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtype( subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); if (lastResortKeyboardSubtype != null) { applicableSubtypes.add(lastResortKeyboardSubtype); @@ -244,7 +242,7 @@ final class SubtypeUtils { * * @return the most applicable subtypeId */ - static InputMethodSubtype findLastResortApplicableSubtypeLocked( + static InputMethodSubtype findLastResortApplicableSubtype( List<InputMethodSubtype> subtypes, String mode, @NonNull String locale, boolean canIgnoreLocaleAsLastResort) { if (subtypes == null || subtypes.isEmpty()) { diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 25a39cc8456f..86d05d92c95b 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -257,7 +257,7 @@ final class IdmapManager { private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage, @NonNull AndroidPackage overlayPackage, int userId) { String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName(); - if (targetOverlayableName != null) { + if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) { try { OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget( targetPackage.getPackageName(), targetOverlayableName, userId); diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index b9464d96a019..a61b03fdbb39 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -32,6 +32,7 @@ import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_RRO; import static android.os.Trace.traceBegin; import static android.os.Trace.traceEnd; + import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException; import android.annotation.NonNull; @@ -362,7 +363,7 @@ public final class OverlayManagerService extends SystemService { defaultPackages.add(packageName); } } - return defaultPackages.toArray(new String[defaultPackages.size()]); + return defaultPackages.toArray(new String[0]); } private final class OverlayManagerPackageMonitor extends PackageMonitor { @@ -1143,9 +1144,10 @@ public final class OverlayManagerService extends SystemService { }; private static final class PackageManagerHelperImpl implements PackageManagerHelper { - private static class PackageStateUsers { + private static final class PackageStateUsers { private PackageState mPackageState; - private final Set<Integer> mInstalledUsers = new ArraySet<>(); + private Boolean mDefinesOverlayable = null; + private final ArraySet<Integer> mInstalledUsers = new ArraySet<>(); private PackageStateUsers(@NonNull PackageState packageState) { this.mPackageState = packageState; } @@ -1160,7 +1162,7 @@ public final class OverlayManagerService extends SystemService { // state may lead to contradictions within OMS. Better then to lag // behind until all pending intents have been processed. private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>(); - private final Set<Integer> mInitializedUsers = new ArraySet<>(); + private final ArraySet<Integer> mInitializedUsers = new ArraySet<>(); PackageManagerHelperImpl(Context context) { mContext = context; @@ -1176,8 +1178,7 @@ public final class OverlayManagerService extends SystemService { */ @NonNull public ArrayMap<String, PackageState> initializeForUser(final int userId) { - if (!mInitializedUsers.contains(userId)) { - mInitializedUsers.add(userId); + if (mInitializedUsers.add(userId)) { mPackageManagerInternal.forEachPackageState((packageState -> { if (packageState.getPkg() != null && packageState.getUserStateOrDefault(userId).isInstalled()) { @@ -1196,13 +1197,11 @@ public final class OverlayManagerService extends SystemService { return userPackages; } - @Override - @Nullable - public PackageState getPackageStateForUser(@NonNull final String packageName, + private PackageStateUsers getRawPackageStateForUser(@NonNull final String packageName, final int userId) { final PackageStateUsers pkg = mCache.get(packageName); if (pkg != null && pkg.mInstalledUsers.contains(userId)) { - return pkg.mPackageState; + return pkg; } try { if (!mPackageManager.isPackageAvailable(packageName, userId)) { @@ -1216,8 +1215,14 @@ public final class OverlayManagerService extends SystemService { return addPackageUser(packageName, userId); } - @NonNull - private PackageState addPackageUser(@NonNull final String packageName, + @Override + public PackageState getPackageStateForUser(@NonNull final String packageName, + final int userId) { + final PackageStateUsers pkg = getRawPackageStateForUser(packageName, userId); + return pkg != null ? pkg.mPackageState : null; + } + + private PackageStateUsers addPackageUser(@NonNull final String packageName, final int user) { final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName); if (pkg == null) { @@ -1229,20 +1234,20 @@ public final class OverlayManagerService extends SystemService { } @NonNull - private PackageState addPackageUser(@NonNull final PackageState pkg, + private PackageStateUsers addPackageUser(@NonNull final PackageState pkg, final int user) { PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName()); if (pkgUsers == null) { pkgUsers = new PackageStateUsers(pkg); mCache.put(pkg.getPackageName(), pkgUsers); - } else { + } else if (pkgUsers.mPackageState != pkg) { pkgUsers.mPackageState = pkg; + pkgUsers.mDefinesOverlayable = null; } pkgUsers.mInstalledUsers.add(user); - return pkgUsers.mPackageState; + return pkgUsers; } - @NonNull private void removePackageUser(@NonNull final String packageName, final int user) { final PackageStateUsers pkgUsers = mCache.get(packageName); @@ -1260,15 +1265,15 @@ public final class OverlayManagerService extends SystemService { } } - @Nullable public PackageState onPackageAdded(@NonNull final String packageName, final int userId) { - return addPackageUser(packageName, userId); + final var pu = addPackageUser(packageName, userId); + return pu != null ? pu.mPackageState : null; } - @Nullable public PackageState onPackageUpdated(@NonNull final String packageName, final int userId) { - return addPackageUser(packageName, userId); + final var pu = addPackageUser(packageName, userId); + return pu != null ? pu.mPackageState : null; } public void onPackageRemoved(@NonNull final String packageName, final int userId) { @@ -1308,22 +1313,30 @@ public final class OverlayManagerService extends SystemService { return (pkgs.length == 0) ? null : pkgs[0]; } - @Nullable @Override public OverlayableInfo getOverlayableForTarget(@NonNull String packageName, @NonNull String targetOverlayableName, int userId) throws IOException { - var packageState = getPackageStateForUser(packageName, userId); - var pkg = packageState == null ? null : packageState.getAndroidPackage(); + final var psu = getRawPackageStateForUser(packageName, userId); + final var pkg = (psu == null || psu.mPackageState == null) + ? null : psu.mPackageState.getAndroidPackage(); if (pkg == null) { throw new IOException("Unable to get target package"); } + if (Boolean.FALSE.equals(psu.mDefinesOverlayable)) { + return null; + } + ApkAssets apkAssets = null; try { apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(), ApkAssets.PROPERTY_ONLY_OVERLAYABLES); - return apkAssets.getOverlayableInfo(targetOverlayableName); + if (psu.mDefinesOverlayable == null) { + psu.mDefinesOverlayable = apkAssets.definesOverlayable(); + } + return Boolean.FALSE.equals(psu.mDefinesOverlayable) + ? null : apkAssets.getOverlayableInfo(targetOverlayableName); } finally { if (apkAssets != null) { try { @@ -1337,24 +1350,29 @@ public final class OverlayManagerService extends SystemService { @Override public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) throws IOException { - var packageState = getPackageStateForUser(targetPackageName, userId); - var pkg = packageState == null ? null : packageState.getAndroidPackage(); + final var psu = getRawPackageStateForUser(targetPackageName, userId); + var pkg = (psu == null || psu.mPackageState == null) + ? null : psu.mPackageState.getAndroidPackage(); if (pkg == null) { throw new IOException("Unable to get target package"); } - ApkAssets apkAssets = null; - try { - apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath()); - return apkAssets.definesOverlayable(); - } finally { - if (apkAssets != null) { - try { - apkAssets.close(); - } catch (Throwable ignored) { + if (psu.mDefinesOverlayable == null) { + ApkAssets apkAssets = null; + try { + apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath(), + ApkAssets.PROPERTY_ONLY_OVERLAYABLES); + psu.mDefinesOverlayable = apkAssets.definesOverlayable(); + } finally { + if (apkAssets != null) { + try { + apkAssets.close(); + } catch (Throwable ignored) { + } } } } + return psu.mDefinesOverlayable; } @Override @@ -1545,8 +1563,7 @@ public final class OverlayManagerService extends SystemService { final OverlayPaths frameworkOverlays = mImpl.getEnabledOverlayPaths("android", userId, false); for (final String targetPackageName : targetPackageNames) { - final OverlayPaths.Builder list = new OverlayPaths.Builder(); - list.addAll(frameworkOverlays); + final var list = new OverlayPaths.Builder(frameworkOverlays); if (!"android".equals(targetPackageName)) { list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true)); } @@ -1558,17 +1575,21 @@ public final class OverlayManagerService extends SystemService { final HashSet<String> invalidPackages = new HashSet<>(); pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages); - for (final String targetPackageName : targetPackageNames) { - if (DEBUG) { - Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=[" - + pendingChanges.get(targetPackageName) - + "] userId=" + userId); - } + if (DEBUG || !invalidPackages.isEmpty()) { + for (final String targetPackageName : targetPackageNames) { + if (DEBUG) { + Slog.d(TAG, + "-> Updating overlay: target=" + targetPackageName + " overlays=[" + + pendingChanges.get(targetPackageName) + + "] userId=" + userId); + } - if (invalidPackages.contains(targetPackageName)) { - Slog.e(TAG, TextUtils.formatSimple( - "Failed to change enabled overlays for %s user %d", targetPackageName, - userId)); + if (invalidPackages.contains(targetPackageName)) { + Slog.e(TAG, TextUtils.formatSimple( + "Failed to change enabled overlays for %s user %d", + targetPackageName, + userId)); + } } } return new ArrayList<>(updatedPackages); diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 972c78db9460..c1b6ccc7e25c 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -772,24 +772,20 @@ final class OverlayManagerServiceImpl { OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName, final int userId, boolean includeImmutableOverlays) { - final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, - userId); - final OverlayPaths.Builder paths = new OverlayPaths.Builder(); - final int n = overlays.size(); - for (int i = 0; i < n; i++) { - final OverlayInfo oi = overlays.get(i); + final var paths = new OverlayPaths.Builder(); + mSettings.forEachMatching(userId, null, targetPackageName, oi -> { if (!oi.isEnabled()) { - continue; + return; } if (!includeImmutableOverlays && !oi.isMutable) { - continue; + return; } if (oi.isFabricated()) { paths.addNonApkPath(oi.baseCodePath); } else { paths.addApkPath(oi.baseCodePath); } - } + }); return paths.build(); } diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java index eae614ac9e77..b8b49f3eed2f 100644 --- a/services/core/java/com/android/server/om/OverlayManagerSettings.java +++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java @@ -47,6 +47,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; @@ -182,6 +183,23 @@ final class OverlayManagerSettings { return CollectionUtils.map(items, SettingsItem::getOverlayInfo); } + void forEachMatching(int userId, String overlayName, String targetPackageName, + @NonNull Consumer<OverlayInfo> consumer) { + for (int i = 0, n = mItems.size(); i < n; i++) { + final SettingsItem item = mItems.get(i); + if (item.getUserId() != userId) { + continue; + } + if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) { + continue; + } + if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) { + continue; + } + consumer.accept(item.getOverlayInfo()); + } + } + ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) { final List<SettingsItem> items = selectWhereUser(userId); diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index f3df4244c47f..cc4c2b5bf893 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -1031,12 +1031,12 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, private void recomputeComponentVisibility( ArrayMap<String, ? extends PackageStateInternal> existingSettings) { final WatchedArraySet<String> protectedBroadcasts; - final WatchedArraySet<Integer> forceQueryable; + final ArraySet<Integer> forceQueryable; synchronized (mProtectedBroadcastsLock) { - protectedBroadcasts = mProtectedBroadcasts.snapshot(); + protectedBroadcasts = new WatchedArraySet<String>(mProtectedBroadcasts); } synchronized (mForceQueryableLock) { - forceQueryable = mForceQueryable.snapshot(); + forceQueryable = new ArraySet<Integer>(mForceQueryable.untrackedStorage()); } final ParallelComputeComponentVisibility computer = new ParallelComputeComponentVisibility( existingSettings, forceQueryable, protectedBroadcasts); diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java index 200734b37269..a02a1bc13b17 100644 --- a/services/core/java/com/android/server/pm/AppsFilterUtils.java +++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java @@ -198,12 +198,12 @@ final class AppsFilterUtils { private static final int MAX_THREADS = 4; private final ArrayMap<String, ? extends PackageStateInternal> mExistingSettings; - private final WatchedArraySet<Integer> mForceQueryable; + private final ArraySet<Integer> mForceQueryable; private final WatchedArraySet<String> mProtectedBroadcasts; ParallelComputeComponentVisibility( @NonNull ArrayMap<String, ? extends PackageStateInternal> existingSettings, - @NonNull WatchedArraySet<Integer> forceQueryable, + @NonNull ArraySet<Integer> forceQueryable, @NonNull WatchedArraySet<String> protectedBroadcasts) { mExistingSettings = existingSettings; mForceQueryable = forceQueryable; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 4f9eab91f7a8..f311034a4dd2 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2900,6 +2900,12 @@ final class InstallPackageHelper { // code is loaded by a new Activity before ApplicationInfo changes have // propagated to all application threads. mPm.scheduleDeferredNoKillPostDelete(args); + if (Flags.improveInstallDontKill()) { + synchronized (mPm.mInstallLock) { + PackageManagerServiceUtils.linkSplitsToOldDirs(mPm.mInstaller, + packageName, pkgSetting.getPath(), pkgSetting.getOldPaths()); + } + } } else { mRemovePackageHelper.cleanUpResources(packageName, args.getCodeFile(), args.getInstructionSets()); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index ac826afc1d22..b5346a351f38 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1680,9 +1680,8 @@ public class LauncherAppsService extends SystemService { mContext, /* requestCode */ 0, intent, - PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_IMMUTABLE - | PendingIntent.FLAG_CANCEL_CURRENT, + PendingIntent.FLAG_IMMUTABLE + | FLAG_UPDATE_CURRENT, /* options */ null, user); return pi == null ? null : pi.getIntentSender(); diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 1a20c8ddc32f..32f56463c8de 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -195,6 +195,7 @@ public class PackageArchiver { Computer snapshot = mPm.snapshotComputer(); int userId = userHandle.getIdentifier(); int binderUid = Binder.getCallingUid(); + int binderPid = Binder.getCallingPid(); if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) { verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid); } @@ -229,7 +230,8 @@ public class PackageArchiver { DELETE_ARCHIVE | DELETE_KEEP_DATA, intentSender, userId, - binderUid); + binderUid, + binderPid); }) .exceptionally( e -> { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index cfafe7cc00df..c6d448d97673 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1405,11 +1405,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements flags, statusReceiver, userId, - Binder.getCallingUid()); + Binder.getCallingUid(), + Binder.getCallingPid()); } void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags, - IntentSender statusReceiver, int userId, int callingUid) { + IntentSender statusReceiver, int userId, int callingUid, int callingPid) { final Computer snapshot = mPm.snapshotComputer(); snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall"); if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) { @@ -1426,7 +1427,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, statusReceiver, versionedPackage.getPackageName(), canSilentlyInstallPackage, userId, mPackageArchiver, flags); - if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES) + if (mContext.checkPermission(Manifest.permission.DELETE_PACKAGES, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) { // Sweet, call straight through! mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags); @@ -1446,8 +1447,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } else { ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId); if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES, - null); + mContext.enforcePermission(Manifest.permission.REQUEST_DELETE_PACKAGES, callingPid, + callingUid, null); } // Take a short detour to confirm with user diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 1a0e8079996e..27c3dad23450 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -54,6 +54,7 @@ import static com.android.internal.util.XmlUtils.writeStringAttribute; import static com.android.internal.util.XmlUtils.writeUriAttribute; import static com.android.server.pm.PackageInstallerService.prepareStageDir; import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME; +import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE; import static com.android.server.pm.PackageManagerServiceUtils.isInstalledByAdb; import static com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata; @@ -1832,7 +1833,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { Os.link(path, sourcePath); // Grant READ access for APK to be read successfully - Os.chmod(sourcePath, 0644); + Os.chmod(sourcePath, DEFAULT_FILE_ACCESS_MODE); } catch (ErrnoException e) { e.rethrowAsIOException(); } @@ -1901,7 +1902,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // If file is app metadata then set permission to 0640 to deny user read access since it // might contain sensitive information. - int mode = name.equals(APP_METADATA_FILE_NAME) ? APP_METADATA_FILE_ACCESS_MODE : 0644; + int mode = name.equals(APP_METADATA_FILE_NAME) + ? APP_METADATA_FILE_ACCESS_MODE : DEFAULT_FILE_ACCESS_MODE; ParcelFileDescriptor targetPfd = openTargetInternal(target.getAbsolutePath(), O_CREAT | O_WRONLY, mode); Os.chmod(target.getAbsolutePath(), mode); @@ -4246,7 +4248,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new IOException("Failed to copy " + fromFile + " to " + tmpFile); } try { - Os.chmod(tmpFile.getAbsolutePath(), 0644); + Os.chmod(tmpFile.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE); } catch (ErrnoException e) { throw new IOException("Failed to chmod " + tmpFile); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 609b3aa3f43d..46f55238163c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -593,6 +593,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService static final String APP_METADATA_FILE_NAME = "app.metadata"; + static final int DEFAULT_FILE_ACCESS_MODE = 0644; + final Handler mHandler; final Handler mBackgroundHandler; @@ -4334,11 +4336,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService mDirtyUsers.remove(userId); } mUserNeedsBadging.delete(userId); - mPermissionManager.onUserRemoved(userId); + mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId); mSettings.removeUserLPw(userId); mPendingBroadcasts.remove(userId); - mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId); mAppsFilter.onUserDeleted(snapshotComputer(), userId); + mPermissionManager.onUserRemoved(userId); } mInstantAppRegistry.onUserRemoved(userId); mPackageMonitorCallbackHelper.onUserRemoved(userId); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index cd3416348153..85316920f363 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -30,6 +30,7 @@ import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION; import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION; import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING; import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED; +import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE; import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX; import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX; import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME; @@ -69,6 +70,7 @@ import android.os.Debug; import android.os.Environment; import android.os.FileUtils; import android.os.Process; +import android.os.SELinux; import android.os.SystemProperties; import android.os.UserHandle; import android.os.incremental.IncrementalManager; @@ -129,10 +131,12 @@ import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.zip.GZIPInputStream; @@ -853,7 +857,7 @@ public class PackageManagerServiceUtils { FileUtils.copy(fileIn, outputStream); // Flush anything in buffer before chmod, because any writes after chmod will fail. outputStream.flush(); - Os.fchmod(outputStream.getFD(), 0644); + Os.fchmod(outputStream.getFD(), DEFAULT_FILE_ACCESS_MODE); atomicFile.finishWrite(outputStream); return PackageManager.INSTALL_SUCCEEDED; } catch (IOException e) { @@ -1081,8 +1085,8 @@ public class PackageManagerServiceUtils { final File targetFile = new File(targetDir, targetName); final FileDescriptor targetFd = Os.open(targetFile.getAbsolutePath(), - O_RDWR | O_CREAT, 0644); - Os.chmod(targetFile.getAbsolutePath(), 0644); + O_RDWR | O_CREAT, DEFAULT_FILE_ACCESS_MODE); + Os.chmod(targetFile.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE); FileInputStream source = null; try { source = new FileInputStream(sourcePath); @@ -1552,4 +1556,72 @@ public class PackageManagerServiceUtils { public static boolean isInstalledByAdb(String initiatingPackageName) { return initiatingPackageName == null || SHELL_PACKAGE_NAME.equals(initiatingPackageName); } + + public static void linkSplitsToOldDirs(@NonNull Installer installer, + @NonNull String packageName, + @NonNull File newPath, + @Nullable Set<File> oldPaths) { + if (oldPaths == null || oldPaths.isEmpty()) { + return; + } + if (IncrementalManager.isIncrementalPath(newPath.getPath())) { + //TODO(b/291212866): handle incremental installs + return; + } + final File[] filesInNewPath = newPath.listFiles(); + if (filesInNewPath == null || filesInNewPath.length == 0) { + return; + } + final List<String> splitApkNames = new ArrayList<String>(); + for (int i = 0; i < filesInNewPath.length; i++) { + if (!filesInNewPath[i].isDirectory() && filesInNewPath[i].toString().endsWith(".apk")) { + splitApkNames.add(filesInNewPath[i].getName()); + } + } + final int numSplits = splitApkNames.size(); + if (numSplits == 0) { + return; + } + for (File oldPath : oldPaths) { + if (!oldPath.exists()) { + continue; + } + for (int i = 0; i < numSplits; i++) { + final String splitApkName = splitApkNames.get(i); + final File linkedSplit = new File(oldPath, splitApkName); + if (linkedSplit.exists()) { + if (DEBUG) { + Slog.d(PackageManagerService.TAG, "Skipping existing linked split <" + + linkedSplit + ">"); + } + continue; + } + final File sourceSplit = new File(newPath, splitApkName); + try { + installer.linkFile(packageName, splitApkName, + newPath.getAbsolutePath(), oldPath.getAbsolutePath()); + if (DEBUG) { + Slog.d(PackageManagerService.TAG, "Linked <" + + sourceSplit + "> to <" + linkedSplit + ">"); + } + } catch (Installer.InstallerException e) { + Slog.w(PackageManagerService.TAG, "Failed to link split <" + + sourceSplit + " > to <" + linkedSplit + ">", e); + continue; + } + try { + Os.chmod(linkedSplit.getAbsolutePath(), DEFAULT_FILE_ACCESS_MODE); + } catch (ErrnoException e) { + Slog.w(PackageManagerService.TAG, "Failed to set mode for linked split <" + + linkedSplit + ">", e); + continue; + } + if (!SELinux.restorecon(linkedSplit)) { + Slog.w(PackageManagerService.TAG, "Failed to restorecon for linked split <" + + linkedSplit + ">"); + } + } + } + //TODO(b/291212866): support native libs + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index bdde272cc46f..5c9c8c6d249a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS; import static android.content.pm.PackageManager.RESTRICTION_NONE; import static com.android.server.LocalManagerRegistry.ManagerNotFoundException; +import static com.android.server.pm.PackageManagerService.DEFAULT_FILE_ACCESS_MODE; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import android.accounts.IAccountManager; @@ -2349,7 +2350,7 @@ class PackageManagerShellCommand extends ShellCommand { Streams.copy(inStream, outStream); } // Give read permissions to the other group. - Os.chmod(outputProfilePath, /*mode*/ 0644 ); + Os.chmod(outputProfilePath, /*mode*/ DEFAULT_FILE_ACCESS_MODE); } catch (IOException | ErrnoException e) { pw.println("Error when reading the profile fd: " + e.getMessage()); e.printStackTrace(pw); @@ -2678,7 +2679,7 @@ class PackageManagerShellCommand extends ShellCommand { } final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL, "runSetStoppedState"); - mInterface.setPackageStoppedState(pkg, state, userId); + mInterface.setPackageStoppedState(pkg, state, translatedUserId); getOutPrintWriter().println("Package " + pkg + " new stopped state: " + mInterface.isPackageStoppedForUser(pkg, translatedUserId)); return 0; diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java index c73728393016..f7603b5cfb57 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateInternal.java @@ -27,6 +27,8 @@ import com.android.server.pm.InstallSource; import com.android.server.pm.PackageKeySetData; import com.android.server.pm.permission.LegacyPermissionState; +import java.io.File; +import java.util.Set; import java.util.UUID; /** @@ -111,4 +113,7 @@ public interface PackageStateInternal extends PackageState { */ @Nullable String getAppMetadataFilePath(); + + @Nullable + Set<File> getOldPaths(); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index bf669fba82ce..0abf304c34ee 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5634,7 +5634,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */); updateScreenOffSleepToken(false /* acquire */, false /* isSwappingDisplay */); - mDefaultDisplayPolicy.screenTurnedOn(screenOnListener); + mDefaultDisplayPolicy.screenTurningOn(screenOnListener); mBootAnimationDismissable = false; synchronized (mLock) { @@ -5676,6 +5676,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.onScreenTurnedOn(); } } + mDefaultDisplayPolicy.screenTurnedOn(); reportScreenStateToVrManager(true); } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index 871e98bf4ab3..4bf8a78a1f16 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -319,6 +319,11 @@ public final class ShutdownThread extends Thread { pd.setMax(100); pd.setProgress(0); pd.setIndeterminate(false); + boolean showPercent = context.getResources().getBoolean( + com.android.internal.R.bool.config_showPercentageTextDuringRebootToUpdate); + if (!showPercent) { + pd.setProgressPercentFormat(null); + } pd.setProgressNumberFormat(null); pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setMessage(context.getText( @@ -911,4 +916,4 @@ public final class ShutdownThread extends Thread { com.android.internal.R.string.config_defaultShutdownVibrationFile); } } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index b384725711c4..92b57645b9a3 100755 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -309,6 +309,24 @@ class TvInputHardwareManager implements TvInputHal.Callback { } } + public SparseArray<String> getHardwareInputIdMap() { + synchronized (mLock) { + return mHardwareInputIdMap.clone(); + } + } + + public SparseArray<String> getHdmiInputIdMap() { + synchronized (mLock) { + return mHdmiInputIdMap.clone(); + } + } + + public Map<String, TvInputInfo> getInputMap() { + synchronized (mLock) { + return Collections.unmodifiableMap(mInputMap); + } + } + public Map<String, List<String>> getHdmiParentInputMap() { synchronized (mLock) { return Collections.unmodifiableMap(mHdmiParentInputMap); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index ac1b4df930eb..e434df7836c4 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -135,6 +135,7 @@ public final class TvInputManagerService extends SystemService { private static final int APP_TAG_SELF = TunedInfo.APP_TAG_SELF; private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS = "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"; + private static final long UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS = 10 * 1000; // 10 seconds // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d, // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the @@ -174,7 +175,7 @@ public final class TvInputManagerService extends SystemService { @GuardedBy("mLock") private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>(); - private final WatchLogHandler mWatchLogHandler; + private final MessageHandler mMessageHandler; private final ActivityManager mActivityManager; @@ -187,8 +188,8 @@ public final class TvInputManagerService extends SystemService { super(context); mContext = context; - mWatchLogHandler = new WatchLogHandler(mContext.getContentResolver(), - IoThread.get().getLooper()); + mMessageHandler = + new MessageHandler(mContext.getContentResolver(), IoThread.get().getLooper()); mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener()); mActivityManager = @@ -372,10 +373,10 @@ public final class TvInputManagerService extends SystemService { // service to populate the hardware list. serviceState = new ServiceState(component, userId); userState.serviceStateMap.put(component, serviceState); + updateServiceConnectionLocked(component, userId); } else { inputList.addAll(serviceState.hardwareInputMap.values()); } - updateServiceConnectionLocked(component, userId); } else { try { TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build(); @@ -510,6 +511,7 @@ public final class TvInputManagerService extends SystemService { } } + @GuardedBy("mLock") private void startProfileLocked(int userId) { mRunningProfiles.add(userId); buildTvInputListLocked(userId, null); @@ -538,8 +540,10 @@ public final class TvInputManagerService extends SystemService { mCurrentUserId = userId; buildTvInputListLocked(userId, null); buildTvContentRatingSystemListLocked(userId); - mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_SWITCH_CONTENT_RESOLVER, - getContentResolverForUser(userId)).sendToTarget(); + mMessageHandler + .obtainMessage(MessageHandler.MSG_SWITCH_CONTENT_RESOLVER, + getContentResolverForUser(userId)) + .sendToTarget(); } } @@ -593,7 +597,7 @@ public final class TvInputManagerService extends SystemService { Slog.e(TAG, "error in unregisterCallback", e); } } - mContext.unbindService(serviceState.connection); + unbindService(serviceState); it.remove(); } } @@ -661,7 +665,7 @@ public final class TvInputManagerService extends SystemService { Slog.e(TAG, "error in unregisterCallback", e); } } - mContext.unbindService(serviceState.connection); + unbindService(serviceState); } } userState.serviceStateMap.clear(); @@ -774,7 +778,8 @@ public final class TvInputManagerService extends SystemService { boolean shouldBind; if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) { - shouldBind = !serviceState.sessionTokens.isEmpty() || serviceState.isHardware; + shouldBind = !serviceState.sessionTokens.isEmpty() + || (serviceState.isHardware && serviceState.neverConnected); } else { // For a non-current user, // if sessionTokens is not empty, it contains recording sessions only @@ -783,31 +788,14 @@ public final class TvInputManagerService extends SystemService { shouldBind = !serviceState.sessionTokens.isEmpty(); } - if (serviceState.service == null && shouldBind) { - // This means that the service is not yet connected but its state indicates that we - // have pending requests. Then, connect the service. - if (serviceState.bound) { - // We have already bound to the service so we don't try to bind again until after we - // unbind later on. - return; - } - if (DEBUG) { - Slog.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")"); - } - - Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(component); - serviceState.bound = mContext.bindServiceAsUser( - i, serviceState.connection, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, - new UserHandle(userId)); - } else if (serviceState.service != null && !shouldBind) { - // This means that the service is already connected but its state indicates that we have - // nothing to do with it. Then, disconnect the service. - if (DEBUG) { - Slog.d(TAG, "unbindService(service=" + component + ")"); + // only bind/unbind when necessary. + if (shouldBind && !serviceState.bound) { + bindService(serviceState, userId); + } else if (!shouldBind && serviceState.bound) { + unbindService(serviceState); + if (!serviceState.isHardware) { + userState.serviceStateMap.remove(component); } - mContext.unbindService(serviceState.connection); - userState.serviceStateMap.remove(component); } } @@ -829,7 +817,11 @@ public final class TvInputManagerService extends SystemService { sendSessionTokenToClientLocked(sessionState.client, sessionState.inputId, null, null, sessionState.seq); } - updateServiceConnectionLocked(serviceState.component, userId); + if (!serviceState.isHardware) { + updateServiceConnectionLocked(serviceState.component, userId); + } else { + updateHardwareServiceConnectionDelayed(userId); + } } @GuardedBy("mLock") @@ -948,13 +940,17 @@ public final class TvInputManagerService extends SystemService { if (serviceState != null) { serviceState.sessionTokens.remove(sessionToken); } - updateServiceConnectionLocked(sessionState.componentName, userId); + if (!serviceState.isHardware) { + updateServiceConnectionLocked(sessionState.componentName, userId); + } else { + updateHardwareServiceConnectionDelayed(userId); + } // Log the end of watch. SomeArgs args = SomeArgs.obtain(); args.arg1 = sessionToken; args.arg2 = System.currentTimeMillis(); - mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_END, args).sendToTarget(); + mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_END, args).sendToTarget(); } @GuardedBy("mLock") @@ -1153,8 +1149,7 @@ public final class TvInputManagerService extends SystemService { ServiceState serviceState = userState.serviceStateMap.get(inputState.info.getComponent()); int oldState = inputState.state; inputState.state = state; - if (serviceState != null && serviceState.service == null - && (!serviceState.sessionTokens.isEmpty() || serviceState.isHardware)) { + if (serviceState != null && serviceState.reconnecting) { // We don't notify state change while reconnecting. It should remain disconnected. return; } @@ -1881,7 +1876,7 @@ public final class TvInputManagerService extends SystemService { args.arg3 = ContentUris.parseId(channelUri); args.arg4 = params; args.arg5 = sessionToken; - mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args) + mMessageHandler.obtainMessage(MessageHandler.MSG_LOG_WATCH_START, args) .sendToTarget(); } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in tune", e); @@ -3327,16 +3322,21 @@ public final class TvInputManagerService extends SystemService { private final ComponentName component; private final boolean isHardware; private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>(); + private final List<TvInputHardwareInfo> hardwareDeviceRemovedBuffer = new ArrayList<>(); + private final List<HdmiDeviceInfo> hdmiDeviceRemovedBuffer = new ArrayList<>(); + private final List<HdmiDeviceInfo> hdmiDeviceUpdatedBuffer = new ArrayList<>(); private ITvInputService service; private ServiceCallback callback; private boolean bound; private boolean reconnecting; + private boolean neverConnected; private ServiceState(ComponentName component, int userId) { this.component = component; this.connection = new InputServiceConnection(component, userId); this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component); + this.neverConnected = true; } } @@ -3449,6 +3449,97 @@ public final class TvInputManagerService extends SystemService { } } + @GuardedBy("mLock") + private void bindService(ServiceState serviceState, int userId) { + if (serviceState.bound) { + // We have already bound to the service so we don't try to bind again until after we + // unbind later on. + // For hardware services, call updateHardwareServiceConnectionDelayed() to delay the + // possible unbinding. + if (serviceState.isHardware) { + updateHardwareServiceConnectionDelayed(userId); + } + return; + } + if (DEBUG) { + Slog.d(TAG, + "bindServiceAsUser(service=" + serviceState.component + ", userId=" + userId + + ")"); + } + Intent i = + new Intent(TvInputService.SERVICE_INTERFACE).setComponent(serviceState.component); + serviceState.bound = mContext.bindServiceAsUser(i, serviceState.connection, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, + new UserHandle(userId)); + if (!serviceState.bound) { + Slog.e(TAG, "failed to bind " + serviceState.component + " for userId " + userId); + mContext.unbindService(serviceState.connection); + } + } + + @GuardedBy("mLock") + private void unbindService(ServiceState serviceState) { + if (!serviceState.bound) { + return; + } + if (DEBUG) { + Slog.d(TAG, "unbindService(service=" + serviceState.component + ")"); + } + mContext.unbindService(serviceState.connection); + serviceState.bound = false; + serviceState.service = null; + serviceState.callback = null; + } + + @GuardedBy("mLock") + private void updateHardwareTvInputServiceBindingLocked(int userId) { + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> services = + pm.queryIntentServicesAsUser(new Intent(TvInputService.SERVICE_INTERFACE), + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, userId); + for (ResolveInfo ri : services) { + ServiceInfo si = ri.serviceInfo; + if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) { + continue; + } + ComponentName component = new ComponentName(si.packageName, si.name); + if (hasHardwarePermission(pm, component)) { + updateServiceConnectionLocked(component, userId); + } + } + } + + private void updateHardwareServiceConnectionDelayed(int userId) { + mMessageHandler.removeMessages(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = userId; + Message msg = + mMessageHandler.obtainMessage(MessageHandler.MSG_UPDATE_HARDWARE_TIS_BINDING, args); + mMessageHandler.sendMessageDelayed(msg, UPDATE_HARDWARE_TIS_BINDING_DELAY_IN_MILLIS); + } + + @GuardedBy("mLock") + private void addHardwareInputLocked( + TvInputInfo inputInfo, ComponentName component, int userId) { + ServiceState serviceState = getServiceStateLocked(component, userId); + serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo); + buildTvInputListLocked(userId, null); + } + + @GuardedBy("mLock") + private void removeHardwareInputLocked(String inputId, int userId) { + if (!mTvInputHardwareManager.getInputMap().containsKey(inputId)) { + return; + } + ComponentName component = mTvInputHardwareManager.getInputMap().get(inputId).getComponent(); + ServiceState serviceState = getServiceStateLocked(component, userId); + boolean removed = serviceState.hardwareInputMap.remove(inputId) != null; + if (removed) { + buildTvInputListLocked(userId, null); + mTvInputHardwareManager.removeHardwareInput(inputId); + } + } + private final class InputServiceConnection implements ServiceConnection { private final ComponentName mComponent; private final int mUserId; @@ -3472,6 +3563,7 @@ public final class TvInputManagerService extends SystemService { } ServiceState serviceState = userState.serviceStateMap.get(mComponent); serviceState.service = ITvInputService.Stub.asInterface(service); + serviceState.neverConnected = false; // Register a callback, if we need to. if (serviceState.isHardware && serviceState.callback == null) { @@ -3483,19 +3575,6 @@ public final class TvInputManagerService extends SystemService { } } - List<IBinder> tokensToBeRemoved = new ArrayList<>(); - - // And create sessions, if any. - for (IBinder sessionToken : serviceState.sessionTokens) { - if (!createSessionInternalLocked(serviceState.service, sessionToken, mUserId)) { - tokensToBeRemoved.add(sessionToken); - } - } - - for (IBinder sessionToken : tokensToBeRemoved) { - removeSessionStateLocked(sessionToken, mUserId); - } - for (TvInputState inputState : userState.inputMap.values()) { if (inputState.info.getComponent().equals(component) && inputState.state != INPUT_STATE_CONNECTED) { @@ -3505,7 +3584,24 @@ public final class TvInputManagerService extends SystemService { } if (serviceState.isHardware) { - serviceState.hardwareInputMap.clear(); + for (TvInputHardwareInfo hardwareToBeRemoved : + serviceState.hardwareDeviceRemovedBuffer) { + try { + serviceState.service.notifyHardwareRemoved(hardwareToBeRemoved); + } catch (RemoteException e) { + Slog.e(TAG, "error in hardwareDeviceRemovedBuffer", e); + } + } + serviceState.hardwareDeviceRemovedBuffer.clear(); + for (HdmiDeviceInfo hdmiDeviceToBeRemoved : + serviceState.hdmiDeviceRemovedBuffer) { + try { + serviceState.service.notifyHdmiDeviceRemoved(hdmiDeviceToBeRemoved); + } catch (RemoteException e) { + Slog.e(TAG, "error in hdmiDeviceRemovedBuffer", e); + } + } + serviceState.hdmiDeviceRemovedBuffer.clear(); for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) { try { serviceState.service.notifyHardwareAdded(hardware); @@ -3520,6 +3616,32 @@ public final class TvInputManagerService extends SystemService { Slog.e(TAG, "error in notifyHdmiDeviceAdded", e); } } + for (HdmiDeviceInfo hdmiDeviceToBeUpdated : + serviceState.hdmiDeviceUpdatedBuffer) { + try { + serviceState.service.notifyHdmiDeviceUpdated(hdmiDeviceToBeUpdated); + } catch (RemoteException e) { + Slog.e(TAG, "error in hdmiDeviceUpdatedBuffer", e); + } + } + serviceState.hdmiDeviceUpdatedBuffer.clear(); + } + + List<IBinder> tokensToBeRemoved = new ArrayList<>(); + + // And create sessions, if any. + for (IBinder sessionToken : serviceState.sessionTokens) { + if (!createSessionInternalLocked(serviceState.service, sessionToken, mUserId)) { + tokensToBeRemoved.add(sessionToken); + } + } + + for (IBinder sessionToken : tokensToBeRemoved) { + removeSessionStateLocked(sessionToken, mUserId); + } + + if (serviceState.isHardware) { + updateHardwareServiceConnectionDelayed(mUserId); } } } @@ -3570,13 +3692,6 @@ public final class TvInputManagerService extends SystemService { } } - @GuardedBy("mLock") - private void addHardwareInputLocked(TvInputInfo inputInfo) { - ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); - serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo); - buildTvInputListLocked(mUserId, null); - } - public void addHardwareInput(int deviceId, TvInputInfo inputInfo) { ensureHardwarePermission(); ensureValidInput(inputInfo); @@ -3587,8 +3702,11 @@ public final class TvInputManagerService extends SystemService { if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) { return; } + Slog.d("ServiceCallback", + "addHardwareInput: device id " + deviceId + ", " + + inputInfo.toString()); mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo); - addHardwareInputLocked(inputInfo); + addHardwareInputLocked(inputInfo, mComponent, mUserId); } } finally { Binder.restoreCallingIdentity(identity); @@ -3606,7 +3724,7 @@ public final class TvInputManagerService extends SystemService { return; } mTvInputHardwareManager.addHdmiInput(id, inputInfo); - addHardwareInputLocked(inputInfo); + addHardwareInputLocked(inputInfo, mComponent, mUserId); if (mOnScreenInputId != null && mOnScreenSessionState != null) { if (TextUtils.equals(mOnScreenInputId, inputInfo.getParentId())) { // catch the use case when a CEC device is plugged in an HDMI port, @@ -3635,14 +3753,9 @@ public final class TvInputManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); - boolean removed = serviceState.hardwareInputMap.remove(inputId) != null; - if (removed) { - buildTvInputListLocked(mUserId, null); - mTvInputHardwareManager.removeHardwareInput(inputId); - } else { - Slog.e(TAG, "failed to remove input " + inputId); - } + Slog.d("ServiceCallback", + "removeHardwareInput " + inputId + " by " + mComponent); + removeHardwareInputLocked(inputId, mUserId); } } finally { Binder.restoreCallingIdentity(identity); @@ -4226,11 +4339,12 @@ public final class TvInputManagerService extends SystemService { return loggedReason; } + @GuardedBy("mLock") private UserState getUserStateLocked(int userId) { return mUserStates.get(userId); } - private static final class WatchLogHandler extends Handler { + private final class MessageHandler extends Handler { // There are only two kinds of watch events that can happen on the system: // 1. The current TV input session is tuned to a new channel. // 2. The session is released for some reason. @@ -4242,10 +4356,11 @@ public final class TvInputManagerService extends SystemService { static final int MSG_LOG_WATCH_START = 1; static final int MSG_LOG_WATCH_END = 2; static final int MSG_SWITCH_CONTENT_RESOLVER = 3; + static final int MSG_UPDATE_HARDWARE_TIS_BINDING = 4; private ContentResolver mContentResolver; - WatchLogHandler(ContentResolver contentResolver, Looper looper) { + MessageHandler(ContentResolver contentResolver, Looper looper) { super(looper); mContentResolver = contentResolver; } @@ -4304,6 +4419,14 @@ public final class TvInputManagerService extends SystemService { mContentResolver = (ContentResolver) msg.obj; break; } + case MSG_UPDATE_HARDWARE_TIS_BINDING: + SomeArgs args = (SomeArgs) msg.obj; + int userId = (int) args.arg1; + synchronized (mLock) { + updateHardwareTvInputServiceBindingLocked(userId); + } + args.recycle(); + break; default: { Slog.w(TAG, "unhandled message code: " + msg.what); break; @@ -4359,29 +4482,46 @@ public final class TvInputManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.isHardware || serviceState.service == null) continue; + if (!serviceState.isHardware) { + continue; + } try { - serviceState.service.notifyHardwareAdded(info); + bindService(serviceState, mCurrentUserId); + if (serviceState.service != null) { + serviceState.service.notifyHardwareAdded(info); + } } catch (RemoteException e) { Slog.e(TAG, "error in notifyHardwareAdded", e); } } + updateHardwareServiceConnectionDelayed(mCurrentUserId); } } @Override public void onHardwareDeviceRemoved(TvInputHardwareInfo info) { synchronized (mLock) { + String relatedInputId = + mTvInputHardwareManager.getHardwareInputIdMap().get(info.getDeviceId()); + removeHardwareInputLocked(relatedInputId, mCurrentUserId); UserState userState = getOrCreateUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.isHardware || serviceState.service == null) continue; + if (!serviceState.isHardware) { + continue; + } try { - serviceState.service.notifyHardwareRemoved(info); + bindService(serviceState, mCurrentUserId); + if (serviceState.service != null) { + serviceState.service.notifyHardwareRemoved(info); + } else { + serviceState.hardwareDeviceRemovedBuffer.add(info); + } } catch (RemoteException e) { Slog.e(TAG, "error in notifyHardwareRemoved", e); } } + updateHardwareServiceConnectionDelayed(mCurrentUserId); } } @@ -4391,29 +4531,46 @@ public final class TvInputManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.isHardware || serviceState.service == null) continue; + if (!serviceState.isHardware) { + continue; + } try { - serviceState.service.notifyHdmiDeviceAdded(deviceInfo); + bindService(serviceState, mCurrentUserId); + if (serviceState.service != null) { + serviceState.service.notifyHdmiDeviceAdded(deviceInfo); + } } catch (RemoteException e) { Slog.e(TAG, "error in notifyHdmiDeviceAdded", e); } } + updateHardwareServiceConnectionDelayed(mCurrentUserId); } } @Override public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) { synchronized (mLock) { + String relatedInputId = + mTvInputHardwareManager.getHdmiInputIdMap().get(deviceInfo.getId()); + removeHardwareInputLocked(relatedInputId, mCurrentUserId); UserState userState = getOrCreateUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.isHardware || serviceState.service == null) continue; + if (!serviceState.isHardware) { + continue; + } try { - serviceState.service.notifyHdmiDeviceRemoved(deviceInfo); + bindService(serviceState, mCurrentUserId); + if (serviceState.service != null) { + serviceState.service.notifyHdmiDeviceRemoved(deviceInfo); + } else { + serviceState.hdmiDeviceRemovedBuffer.add(deviceInfo); + } } catch (RemoteException e) { Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e); } } + updateHardwareServiceConnectionDelayed(mCurrentUserId); } } @@ -4441,13 +4598,21 @@ public final class TvInputManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. for (ServiceState serviceState : userState.serviceStateMap.values()) { - if (!serviceState.isHardware || serviceState.service == null) continue; + if (!serviceState.isHardware) { + continue; + } try { - serviceState.service.notifyHdmiDeviceUpdated(deviceInfo); + bindService(serviceState, mCurrentUserId); + if (serviceState.service != null) { + serviceState.service.notifyHdmiDeviceUpdated(deviceInfo); + } else { + serviceState.hdmiDeviceUpdatedBuffer.add(deviceInfo); + } } catch (RemoteException e) { Slog.e(TAG, "error in notifyHdmiDeviceUpdated", e); } } + updateHardwareServiceConnectionDelayed(mCurrentUserId); } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 51acc8e01cda..8549957f46b8 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -410,9 +410,10 @@ public class WallpaperCropper { // adapt the entries in wallpaper.mCropHints for the actual display SparseArray<Rect> updatedCropHints = new SparseArray<>(); for (int i = 0; i < wallpaper.mCropHints.size(); i++) { - Rect defaultCrop = defaultDisplayCrops.valueAt(i); + int orientation = wallpaper.mCropHints.keyAt(i); + Rect defaultCrop = defaultDisplayCrops.get(orientation); if (defaultCrop != null) { - updatedCropHints.put(defaultDisplayCrops.keyAt(i), defaultCrop); + updatedCropHints.put(orientation, defaultCrop); } } wallpaper.mCropHints = updatedCropHints; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9b1f9c8441ad..036f7b6841c2 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3965,20 +3965,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return removedFromHistory; } - boolean safelyDestroy(String reason) { - if (isDestroyable()) { - if (DEBUG_SWITCH) { - final Task task = getTask(); - Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState() - + " resumed=" + task.getTopResumedActivity() - + " pausing=" + task.getTopPausingActivity() - + " for reason " + reason); - } - return destroyImmediately(reason); - } - return false; - } - /** Note: call {@link #cleanUp(boolean, boolean)} before this method. */ void removeFromHistory(String reason) { finishActivityResults(Activity.RESULT_CANCELED, @@ -4047,10 +4033,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - boolean isFinishing() { - return finishing; - } - /** * This method is to only be called from the client via binder when the activity is destroyed * AND finished. @@ -7978,6 +7960,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) { return; } + final int originalRelaunchingCount = mPendingRelaunchCount; // This is necessary in order to avoid going into size compat mode when the orientation // change request comes from the app if (getRequestedConfigurationOrientation(false, requestedOrientation) @@ -7995,8 +7978,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // the request is handled at task level with letterbox. if (!getMergedOverrideConfiguration().equals( mLastReportedConfiguration.getMergedConfiguration())) { - ensureActivityConfiguration( - false /* ignoreVisibility */, true /* isRequestedOrientationChanged */); + ensureActivityConfiguration(false /* ignoreVisibility */); + if (mPendingRelaunchCount > originalRelaunchingCount) { + mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true); + } if (mTransitionController.inPlayingTransition(this)) { mTransitionController.mValidateActivityCompat.add(this); } @@ -9502,11 +9487,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return ensureActivityConfiguration(false /* ignoreVisibility */); } - boolean ensureActivityConfiguration(boolean ignoreVisibility) { - return ensureActivityConfiguration(ignoreVisibility, - false /* isRequestedOrientationChanged */); - } - /** * Make sure the given activity matches the current configuration. Ensures the HistoryRecord * is updated with the correct configuration and all other bookkeeping is handled. @@ -9515,13 +9495,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * (stopped state). This is useful for the case where we know the * activity will be visible soon and we want to ensure its configuration * before we make it visible. - * @param isRequestedOrientationChanged whether this is triggered in response to an app calling - * {@link android.app.Activity#setRequestedOrientation}. * @return False if the activity was relaunched and true if it wasn't relaunched because we * can't or the app handles the specific configuration that is changing. */ - boolean ensureActivityConfiguration(boolean ignoreVisibility, - boolean isRequestedOrientationChanged) { + boolean ensureActivityConfiguration(boolean ignoreVisibility) { final Task rootTask = getRootTask(); if (rootTask.mConfigWillChange) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check " @@ -9658,9 +9635,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { mRelaunchReason = RELAUNCH_REASON_NONE; } - if (isRequestedOrientationChanged) { - mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true); - } if (mState == PAUSING) { // A little annoying: we are waiting for this activity to finish pausing. Let's not // do anything now, but just flag that it needs to be restarted when done pausing. diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java index f1a2159d6dbe..db27f607c867 100644 --- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java +++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java @@ -43,7 +43,7 @@ class ActivitySecurityModelFeatureFlags { static final String DOC_LINK = "go/android-asm"; /** Used to determine which version of the ASM logic was used in logs while we iterate */ - static final int ASM_VERSION = 8; + static final int ASM_VERSION = 9; private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER; private static final String KEY_ASM_PREFIX = "ActivitySecurity__"; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index f6d77ea33598..d6f52b89819e 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2053,8 +2053,8 @@ class ActivityStarter { } if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart( - mSourceRecord, r, newTask, targetTask, mLaunchFlags, mBalCode, mCallingUid, - mRealCallingUid)) { + mSourceRecord, r, newTask, avoidMoveToFront(), targetTask, mLaunchFlags, mBalCode, + mCallingUid, mRealCallingUid)) { return START_ABORTED; } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 2bd49bfa6219..a4d15e07a3ed 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; +import static android.view.View.FOCUS_FORWARD; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_NONE; @@ -60,6 +61,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; import com.android.server.wm.utils.InsetUtils; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; @@ -167,6 +169,24 @@ class BackNavigationController { return null; } + // Move focus to the adjacent embedded window if it is higher than this window + final TaskFragment taskFragment = window.getTaskFragment(); + final TaskFragment adjacentTaskFragment = + taskFragment != null ? taskFragment.getAdjacentTaskFragment() : null; + if (adjacentTaskFragment != null && taskFragment.isEmbedded() + && Flags.embeddedActivityBackNavFlag()) { + final WindowContainer parent = taskFragment.getParent(); + if (parent.mChildren.indexOf(taskFragment) < parent.mChildren.indexOf( + adjacentTaskFragment)) { + mWindowManagerService.moveFocusToAdjacentWindow(window, FOCUS_FORWARD); + window = wmService.getFocusedWindowLocked(); + if (window == null) { + Slog.e(TAG, "Adjacent window is null, returning null."); + return null; + } + } + } + // This is needed to bridge the old and new back behavior with recents. While in // Overview with live tile enabled, the previous app is technically focused but we // add an input consumer to capture all input that would otherwise go to the apps diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 0f36d8eafbe4..bcb4559d79e4 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -57,6 +57,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Process; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArraySet; @@ -1042,8 +1043,9 @@ public class BackgroundActivityStartController { * create a new task or bring an existing one into the foreground */ boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord, - @NonNull ActivityRecord targetRecord, boolean newTask, @NonNull Task targetTask, - int launchFlags, int balCode, int callingUid, int realCallingUid) { + @NonNull ActivityRecord targetRecord, boolean newTask, boolean avoidMoveTaskToFront, + @Nullable Task targetTask, int launchFlags, int balCode, int callingUid, + int realCallingUid) { // BAL Exception allowed in all cases if (balCode == BAL_ALLOW_ALLOWLISTED_UID) { return true; @@ -1067,14 +1069,36 @@ public class BackgroundActivityStartController { } if (balCode == BAL_ALLOW_GRACE_PERIOD) { + // Allow if launching into new task, and caller matches most recently finished activity if (taskToFront && mTopFinishedActivity != null && mTopFinishedActivity.mUid == callingUid) { return true; - } else if (!taskToFront) { - FinishedActivityEntry finishedEntry = - mTaskIdToFinishedActivity.get(targetTask.mTaskId); - if (finishedEntry != null && finishedEntry.mUid == callingUid) { - return true; + } + + // Launching into existing task - allow if matches most recently finished activity + // within the task. + // We can reach here multiple ways: + // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available) + // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available) + // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true, + // avoidMoveTaskToFront = true, sourceRecord is available) + // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true, + // sourceRecord is not available, targetTask may be available) + if (!taskToFront || avoidMoveTaskToFront) { + if (targetTask != null) { + FinishedActivityEntry finishedEntry = + mTaskIdToFinishedActivity.get(targetTask.mTaskId); + if (finishedEntry != null && finishedEntry.mUid == callingUid) { + return true; + } + } + + if (sourceRecord != null) { + FinishedActivityEntry finishedEntry = + mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId); + if (finishedEntry != null && finishedEntry.mUid == callingUid) { + return true; + } } } } @@ -1098,7 +1122,7 @@ public class BackgroundActivityStartController { bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(), sourceRecord); } - } else if (!taskToFront) { + } else if (targetTask != null && (!taskToFront || avoidMoveTaskToFront)) { // We don't have a sourceRecord, and we're launching into an existing task. // Allow if callingUid is top of stack. bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid, @@ -1111,12 +1135,14 @@ public class BackgroundActivityStartController { // ASM rules have failed. Log why return logAsmFailureAndCheckFeatureEnabled(sourceRecord, callingUid, realCallingUid, - newTask, targetTask, targetRecord, balCode, launchFlags, bas, taskToFront); + newTask, avoidMoveTaskToFront, targetTask, targetRecord, balCode, launchFlags, + bas, taskToFront); } private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord sourceRecord, int callingUid, - int realCallingUid, boolean newTask, Task targetTask, ActivityRecord targetRecord, - @BalCode int balCode, int launchFlags, BlockActivityStart bas, boolean taskToFront) { + int realCallingUid, boolean newTask, boolean avoidMoveTaskToFront, Task targetTask, + ActivityRecord targetRecord, @BalCode int balCode, int launchFlags, + BlockActivityStart bas, boolean taskToFront) { ActivityRecord targetTopActivity = targetTask == null ? null : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop()); @@ -1133,7 +1159,7 @@ public class BackgroundActivityStartController { String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord, targetRecord, targetTask, targetTopActivity, realCallingUid, balCode, - blockActivityStartAndFeatureEnabled, taskToFront); + blockActivityStartAndFeatureEnabled, taskToFront, avoidMoveTaskToFront); FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED, /* caller_uid */ @@ -1265,7 +1291,7 @@ public class BackgroundActivityStartController { Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord, targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart, - /* taskToFront */ true)); + /* taskToFront */ true, /* avoidMoveTaskToFront */ false)); } } @@ -1379,7 +1405,7 @@ public class BackgroundActivityStartController { private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task, int uid, @Nullable ActivityRecord sourceRecord) { // If the source is visible, consider it 'top'. - if (sourceRecord != null && sourceRecord.isVisible()) { + if (sourceRecord != null && sourceRecord.isVisibleRequested()) { return new BlockActivityStart(false, false); } @@ -1389,6 +1415,12 @@ public class BackgroundActivityStartController { return new BlockActivityStart(false, false); } + // If UID is visible in target task, allow launch + if (task.forAllActivities((Predicate<ActivityRecord>) + ar -> ar.isUid(uid) && ar.isVisibleRequested())) { + return new BlockActivityStart(false, false); + } + // Consider the source activity, whether or not it is finishing. Do not consider any other // finishing activity. Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord) @@ -1480,27 +1512,26 @@ public class BackgroundActivityStartController { @Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord, @Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity, int realCallingUid, @BalCode int balCode, - boolean blockActivityStartAndFeatureEnabled, boolean taskToFront) { + boolean blockActivityStartAndFeatureEnabled, boolean taskToFront, + boolean avoidMoveTaskToFront) { final String prefix = "[ASM] "; Function<ActivityRecord, String> recordToString = (ar) -> { if (ar == null) { return null; } - return (ar == sourceRecord ? " [source]=> " + + return (ar == sourceRecord ? " [source]=> " : ar == targetTopActivity ? " [ top ]=> " - : ar == targetRecord ? " [target]=> " - : " => ") - + ar - + " :: visible=" + ar.isVisible() - + ", finishing=" + ar.isFinishing() - + ", alwaysOnTop=" + ar.isAlwaysOnTop() - + ", taskFragment=" + ar.getTaskFragment(); + : ar == targetRecord ? " [target]=> " + : " => ") + + getDebugStringForActivityRecord(ar); }; StringJoiner joiner = new StringJoiner("\n"); joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------"); joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled); joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION); + joiner.add(prefix + "System Time: " + SystemClock.uptimeMillis()); boolean targetTaskMatchesSourceTask = targetTask != null && sourceRecord != null && sourceRecord.getTask() == targetTask; @@ -1512,6 +1543,8 @@ public class BackgroundActivityStartController { joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage); } else { joiner.add(prefix + "Source Record: " + recordToString.apply(sourceRecord)); + joiner.add(prefix + "Source Launch Package: " + sourceRecord.launchedFromPackage); + joiner.add(prefix + "Source Launch Intent: " + sourceRecord.intent); if (targetTaskMatchesSourceTask) { joiner.add(prefix + "Source/Target Task: " + sourceRecord.getTask()); joiner.add(prefix + "Source/Target Task Stack: "); @@ -1536,7 +1569,30 @@ public class BackgroundActivityStartController { joiner.add(prefix + "Target Record: " + recordToString.apply(targetRecord)); joiner.add(prefix + "Intent: " + targetRecord.intent); joiner.add(prefix + "TaskToFront: " + taskToFront); + joiner.add(prefix + "AvoidMoveToFront: " + avoidMoveTaskToFront); joiner.add(prefix + "BalCode: " + balCodeToString(balCode)); + joiner.add(prefix + "LastResumedActivity: " + + recordToString.apply(mService.mLastResumedActivity)); + + if (mTopFinishedActivity != null) { + joiner.add(prefix + "TopFinishedActivity: " + mTopFinishedActivity.mDebugInfo); + } + + if (!mTaskIdToFinishedActivity.isEmpty()) { + joiner.add(prefix + "TaskIdToFinishedActivity: "); + mTaskIdToFinishedActivity.values().forEach( + (fae) -> joiner.add(prefix + " " + fae.mDebugInfo)); + } + + if (balCode == BAL_ALLOW_VISIBLE_WINDOW || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW + || balCode == BAL_ALLOW_FOREGROUND) { + Task task = sourceRecord != null ? sourceRecord.getTask() : targetTask; + if (task != null && task.getDisplayArea() != null) { + joiner.add(prefix + "Tasks: "); + task.getDisplayArea().forAllTasks((Consumer<Task>) + t -> joiner.add(prefix + " T: " + t.toFullString())); + } + } joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------"); return joiner.toString(); @@ -1620,7 +1676,7 @@ public class BackgroundActivityStartController { return; } - if (!finishActivity.mVisibleRequested + if (!finishActivity.isVisibleRequested() && finishActivity != finishActivity.getTask().getTopMostActivity()) { return; } @@ -1666,10 +1722,22 @@ public class BackgroundActivityStartController { } } + private static String getDebugStringForActivityRecord(ActivityRecord ar) { + return ar + + " :: visible=" + ar.isVisible() + + ", visibleRequested=" + ar.isVisibleRequested() + + ", finishing=" + ar.finishing + + ", alwaysOnTop=" + ar.isAlwaysOnTop() + + ", lastLaunchTime=" + ar.lastLaunchTime + + ", lastVisibleTime=" + ar.lastVisibleTime + + ", taskFragment=" + ar.getTaskFragment(); + } + private class FinishedActivityEntry { int mUid; int mTaskId; int mLaunchCount; + String mDebugInfo; FinishedActivityEntry(ActivityRecord ar) { FinishedActivityEntry entry = mTaskIdToFinishedActivity.get(ar.getTask().mTaskId); @@ -1677,6 +1745,7 @@ public class BackgroundActivityStartController { this.mUid = ar.getUid(); this.mTaskId = taskId; this.mLaunchCount = entry == null || !ar.isUid(entry.mUid) ? 1 : entry.mLaunchCount + 1; + this.mDebugInfo = getDebugStringForActivityRecord(ar); mService.mH.postDelayed(() -> { synchronized (mService.mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 63ca5929e34d..e2bc59bb6550 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -794,6 +794,9 @@ public class DisplayPolicy { } mService.mAtmService.mKeyguardController.updateDeferTransitionForAod( mAwake /* waiting */); + if (!awake) { + mDisplayContent.mWallpaperController.onDisplaySwitchFinished(); + } } } @@ -836,7 +839,8 @@ public class DisplayPolicy { mRemoteInsetsControllerControlsSystemBars = remoteInsetsControllerControlsSystemBars; } - public void screenTurnedOn(ScreenOnListener screenOnListener) { + /** Prepares to turn on screen. The given listener is used to notify that it is ready. */ + public void screenTurningOn(ScreenOnListener screenOnListener) { WindowProcessController visibleDozeUiProcess = null; synchronized (mLock) { mScreenOnEarly = true; @@ -858,6 +862,11 @@ public class DisplayPolicy { } } + /** It is called after {@link #finishScreenTurningOn}. This runs on PowerManager's thread. */ + public void screenTurnedOn() { + mDisplayContent.mWallpaperController.onDisplaySwitchFinished(); + } + public void screenTurnedOff() { synchronized (mLock) { mScreenOnEarly = false; diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index fcc1e5b62221..f2796895d639 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -148,7 +148,7 @@ import java.util.function.Predicate; final class LetterboxUiController { private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE = - activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing(); + ActivityRecord::occludesParent; private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 02b3f15979ce..587cc7489763 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2783,6 +2783,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } else { throw new RuntimeException("Create the same sleep token twice: " + token); } + if (isSwappingDisplay) { + display.mWallpaperController.onDisplaySwitchStarted(); + } return token; } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index d68f932400a2..0fc62a758c5e 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WallpaperManager.COMMAND_DISPLAY_SWITCH; import static android.app.WallpaperManager.COMMAND_FREEZE; import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; @@ -120,6 +121,11 @@ class WallpaperController { private boolean mShouldOffsetWallpaperCenter; + /** + * Whether the wallpaper has been notified about a physical display switch event is started. + */ + private volatile boolean mIsWallpaperNotifiedOnDisplaySwitch; + private final Consumer<WindowState> mFindWallpapers = w -> { if (w.mAttrs.type == TYPE_WALLPAPER) { WallpaperWindowToken token = w.mToken.asWallpaperToken(); @@ -1083,6 +1089,52 @@ class WallpaperController { } /** + * Notifies the wallpaper that the display turns off when switching physical device. If the + * wallpaper is currently visible, its client visibility will be preserved until the display is + * confirmed to be off or on. + */ + void onDisplaySwitchStarted() { + mIsWallpaperNotifiedOnDisplaySwitch = notifyDisplaySwitch(true /* start */); + } + + /** + * Called when the screen has finished turning on or the device goes to sleep. This is no-op if + * the operation is not part of a display switch. + */ + void onDisplaySwitchFinished() { + // The method can be called outside WM lock (turned on), so only acquire lock if needed. + // This is to optimize the common cases that regular devices don't have display switch. + if (mIsWallpaperNotifiedOnDisplaySwitch) { + synchronized (mService.mGlobalLock) { + mIsWallpaperNotifiedOnDisplaySwitch = false; + notifyDisplaySwitch(false /* start */); + } + } + } + + private boolean notifyDisplaySwitch(boolean start) { + boolean notified = false; + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); + for (int i = token.getChildCount() - 1; i >= 0; i--) { + final WindowState w = token.getChildAt(i); + if (start && !w.mWinAnimator.getShown()) { + continue; + } + try { + w.mClient.dispatchWallpaperCommand(COMMAND_DISPLAY_SWITCH, 0 /* x */, 0 /* y */, + start ? 1 : 0 /* use z as start or finish */, + null /* bundle */, false /* sync */); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to dispatch COMMAND_DISPLAY_SWITCH " + e); + } + notified = true; + } + } + return notified; + } + + /** * Each window can request a zoom, example: * - User is in overview, zoomed out. * - User also pulls down the shade. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f8ac8da710c8..9650b8bc2281 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9156,55 +9156,63 @@ public class WindowManagerService extends IWindowManager.Stub if (fromWin == null || !fromWin.isFocused()) { return false; } - final TaskFragment fromFragment = fromWin.getTaskFragment(); - if (fromFragment == null) { - return false; - } - final TaskFragment adjacentFragment = fromFragment.getAdjacentTaskFragment(); - if (adjacentFragment == null || adjacentFragment.asTask() != null) { - // Don't move the focus to another task. - return false; - } - final Rect fromBounds = fromFragment.getBounds(); - final Rect adjacentBounds = adjacentFragment.getBounds(); - switch (direction) { - case View.FOCUS_LEFT: - if (adjacentBounds.left >= fromBounds.left) { - return false; - } - break; - case View.FOCUS_UP: - if (adjacentBounds.top >= fromBounds.top) { - return false; - } - break; - case View.FOCUS_RIGHT: - if (adjacentBounds.right <= fromBounds.right) { - return false; - } - break; - case View.FOCUS_DOWN: - if (adjacentBounds.bottom <= fromBounds.bottom) { - return false; - } - break; - case View.FOCUS_BACKWARD: - case View.FOCUS_FORWARD: - // These are not absolute directions. Skip checking the bounds. - break; - default: + return moveFocusToAdjacentWindow(fromWin, direction); + } + } + + boolean moveFocusToAdjacentWindow(WindowState fromWin, @FocusDirection int direction) { + final TaskFragment fromFragment = fromWin.getTaskFragment(); + if (fromFragment == null) { + return false; + } + final TaskFragment adjacentFragment = fromFragment.getAdjacentTaskFragment(); + if (adjacentFragment == null || adjacentFragment.asTask() != null) { + // Don't move the focus to another task. + return false; + } + if (adjacentFragment.isIsolatedNav()) { + // Don't move the focus if the adjacent TF is isolated navigation. + return false; + } + final Rect fromBounds = fromFragment.getBounds(); + final Rect adjacentBounds = adjacentFragment.getBounds(); + switch (direction) { + case View.FOCUS_LEFT: + if (adjacentBounds.left >= fromBounds.left) { return false; - } - final ActivityRecord topRunningActivity = adjacentFragment.topRunningActivity( - true /* focusableOnly */); - if (topRunningActivity == null) { - return false; - } - moveDisplayToTopInternal(topRunningActivity.getDisplayId()); - handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity); - if (fromWin.isFocused()) { + } + break; + case View.FOCUS_UP: + if (adjacentBounds.top >= fromBounds.top) { + return false; + } + break; + case View.FOCUS_RIGHT: + if (adjacentBounds.right <= fromBounds.right) { + return false; + } + break; + case View.FOCUS_DOWN: + if (adjacentBounds.bottom <= fromBounds.bottom) { + return false; + } + break; + case View.FOCUS_BACKWARD: + case View.FOCUS_FORWARD: + // These are not absolute directions. Skip checking the bounds. + break; + default: return false; - } + } + final ActivityRecord topRunningActivity = adjacentFragment.topRunningActivity( + true /* focusableOnly */); + if (topRunningActivity == null) { + return false; + } + moveDisplayToTopInternal(topRunningActivity.getDisplayId()); + handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity); + if (fromWin.isFocused()) { + return false; } return true; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java index 532823ad8367..e8c5658ca941 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK; +import static android.app.admin.flags.Flags.defaultSmsPersonalAppSuspensionFixEnabled; import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.Nullable; @@ -42,6 +43,7 @@ import android.view.accessibility.IAccessibilityManager; import android.view.inputmethod.InputMethodInfo; import com.android.internal.R; +import com.android.internal.telephony.SmsApplication; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.utils.Slogf; @@ -97,7 +99,7 @@ public final class PersonalAppsSuspensionHelper { result.removeAll(getSystemLauncherPackages()); result.removeAll(getAccessibilityServices()); result.removeAll(getInputMethodPackages()); - result.remove(Telephony.Sms.getDefaultSmsPackage(mContext)); + result.remove(getDefaultSmsPackage()); result.remove(getSettingsPackageName()); final String[] unsuspendablePackages = @@ -202,6 +204,17 @@ public final class PersonalAppsSuspensionHelper { return resolveInfos != null && !resolveInfos.isEmpty(); } + private String getDefaultSmsPackage() { + //TODO(b/319449037): Unflag the following change. + if (defaultSmsPersonalAppSuspensionFixEnabled()) { + return SmsApplication.getDefaultSmsApplicationAsUser( + mContext, /*updateIfNeeded=*/ false, mContext.getUser()) + .getPackageName(); + } else { + return Telephony.Sms.getDefaultSmsPackage(mContext); + } + } + void dump(IndentingPrintWriter pw) { pw.println("PersonalAppsSuspensionHelper"); @@ -212,7 +225,7 @@ public final class PersonalAppsSuspensionHelper { DevicePolicyManagerService.dumpApps(pw, "accessibility services", getAccessibilityServices()); DevicePolicyManagerService.dumpApps(pw, "input method packages", getInputMethodPackages()); - pw.printf("SMS package: %s\n", Telephony.Sms.getDefaultSmsPackage(mContext)); + pw.printf("SMS package: %s\n", getDefaultSmsPackage()); pw.printf("Settings package: %s\n", getSettingsPackageName()); DevicePolicyManagerService.dumpApps(pw, "Packages subject to suspension", getPersonalAppsForSuspension()); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java index 23e3e2560524..0edb3dfc0bc0 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java @@ -55,7 +55,7 @@ public final class AdditionalSubtypeUtilsTest { // Save & load. AtomicFile atomicFile = new AtomicFile( new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml")); - AdditionalSubtypeUtils.saveToFile(allSubtypes, methodMap, atomicFile); + AdditionalSubtypeUtils.saveToFile(allSubtypes, InputMethodMap.of(methodMap), atomicFile); ArrayMap<String, List<InputMethodSubtype>> loadedSubtypes = new ArrayMap<>(); AdditionalSubtypeUtils.loadFromFile(loadedSubtypes, atomicFile); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java index 570132f5a7d8..71752ba3b393 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceRestrictImeAmountTest.java @@ -126,11 +126,9 @@ public class InputMethodManagerServiceRestrictImeAmountTest extends List<String> enabledComponents) { final ArrayMap<String, List<InputMethodSubtype>> emptyAdditionalSubtypeMap = new ArrayMap<>(); - final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); - final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); - InputMethodManagerService.filterInputMethodServices(emptyAdditionalSubtypeMap, methodMap, - methodList, enabledComponents, mContext, resolveInfoList); - return methodList; + final InputMethodMap methodMap = InputMethodManagerService.filterInputMethodServices( + emptyAdditionalSubtypeMap, enabledComponents, mContext, resolveInfoList); + return methodMap.values(); } private ResolveInfo createFakeSystemResolveInfo(String packageName, String componentName) { diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index ac485be5b5a1..2857619c70d3 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -269,7 +269,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_EN_US), imi); assertEquals(1, result.size()); verifyEquality(autoSubtype, result.get(0)); @@ -293,7 +293,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_EN_US), imi); assertEquals(2, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); @@ -317,7 +317,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_EN_GB), imi); assertEquals(2, result.size()); verifyEquality(nonAutoEnGB, result.get(0)); @@ -342,7 +342,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_FR), imi); assertEquals(2, result.size()); verifyEquality(nonAutoFrCA, result.get(0)); @@ -363,7 +363,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_FR_CA), imi); assertEquals(2, result.size()); verifyEquality(nonAutoFrCA, result.get(0)); @@ -385,7 +385,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_JA_JP), imi); assertEquals(3, result.size()); verifyEquality(nonAutoJa, result.get(0)); @@ -407,7 +407,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoHi, result.get(0)); @@ -424,7 +424,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); @@ -441,7 +441,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); @@ -463,7 +463,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(Locale.forLanguageTag("sr-Latn-RS")), imi); assertEquals(2, result.size()); assertThat(nonAutoSrLatn, is(in(result))); @@ -483,7 +483,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(Locale.forLanguageTag("sr-Cyrl-RS")), imi); assertEquals(2, result.size()); assertThat(nonAutoSrCyrl, is(in(result))); @@ -509,7 +509,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList( Locale.forLanguageTag("sr-Latn-RS-x-android"), Locale.forLanguageTag("ja-JP"), @@ -536,7 +536,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_FIL_PH), imi); assertEquals(1, result.size()); verifyEquality(nonAutoFil, result.get(0)); @@ -554,7 +554,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_FI), imi); assertEquals(1, result.size()); verifyEquality(nonAutoJa, result.get(0)); @@ -570,7 +570,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_IN), imi); assertEquals(1, result.size()); verifyEquality(nonAutoIn, result.get(0)); @@ -584,7 +584,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_ID), imi); assertEquals(1, result.size()); verifyEquality(nonAutoIn, result.get(0)); @@ -598,7 +598,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_IN), imi); assertEquals(1, result.size()); verifyEquality(nonAutoId, result.get(0)); @@ -612,7 +612,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_ID), imi); assertEquals(1, result.size()); verifyEquality(nonAutoId, result.get(0)); @@ -634,7 +634,7 @@ public final class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - SubtypeUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypes( new LocaleList(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi); assertThat(nonAutoFrCA, is(in(result))); assertThat(nonAutoEnUS, is(in(result))); @@ -796,19 +796,22 @@ public final class InputMethodUtilsTest { { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); - assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, null, "")); + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap), + null, "")); } // Returns null when the config value is empty. { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); - assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, "", "")); + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap), "", + "")); } // Returns null when the configured package doesn't have an IME. { - assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(new ArrayMap<>(), + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme( + InputMethodMap.emptyMap(), systemIme.getPackageName(), "")); } @@ -816,7 +819,8 @@ public final class InputMethodUtilsTest { { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); - assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, + assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme( + InputMethodMap.of(methodMap), systemIme.getPackageName(), null)); } @@ -824,13 +828,15 @@ public final class InputMethodUtilsTest { { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); - assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, + assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme( + InputMethodMap.of(methodMap), systemIme.getPackageName(), "")); } // Returns null when the current default isn't found. { - assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(new ArrayMap<>(), + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme( + InputMethodMap.emptyMap(), systemIme.getPackageName(), systemIme.getId())); } @@ -841,7 +847,7 @@ public final class InputMethodUtilsTest { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); methodMap.put(secondIme.getId(), secondIme); - assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap), systemIme.getPackageName(), "")); } @@ -852,7 +858,8 @@ public final class InputMethodUtilsTest { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); methodMap.put(secondIme.getId(), secondIme); - assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, + assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme( + InputMethodMap.of(methodMap), systemIme.getPackageName(), systemIme.getId())); } @@ -862,7 +869,7 @@ public final class InputMethodUtilsTest { final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme", "fake.voice0", false /* isSystem */); methodMap.put(nonSystemIme.getId(), nonSystemIme); - assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap), nonSystemIme.getPackageName(), nonSystemIme.getId())); } @@ -873,7 +880,7 @@ public final class InputMethodUtilsTest { "FakeDefaultAutoVoiceIme", "fake.voice0", false /* isSystem */); methodMap.put(systemIme.getId(), systemIme); methodMap.put(nonSystemIme.getId(), nonSystemIme); - assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(InputMethodMap.of(methodMap), nonSystemIme.getPackageName(), "")); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java index 4095be74d294..18dc114a8cd1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java @@ -20,11 +20,13 @@ import static com.google.common.truth.Truth.assertThat; import android.annotation.NonNull; import android.app.backup.BackupHelper; +import android.app.backup.BackupHelperWithLogger; import android.content.Context; import android.content.pm.PackageManager; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import static org.mockito.Mockito.when; @@ -32,7 +34,10 @@ import static org.mockito.Mockito.when; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.server.backup.Flags; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -55,6 +60,9 @@ public class SystemBackupAgentTest { @Mock private PackageManager mPackageManagerMock; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -71,7 +79,7 @@ public class SystemBackupAgentTest { mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0); - assertThat(mSystemBackupAgent.mAddedHelpers) + assertThat(mSystemBackupAgent.mAddedHelpersKey) .containsExactly( "account_sync_settings", "preferred_activities", @@ -96,7 +104,7 @@ public class SystemBackupAgentTest { mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0); - assertThat(mSystemBackupAgent.mAddedHelpers) + assertThat(mSystemBackupAgent.mAddedHelpersKey) .containsExactly( "account_sync_settings", "preferred_activities", @@ -118,7 +126,7 @@ public class SystemBackupAgentTest { mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0); - assertThat(mSystemBackupAgent.mAddedHelpers) + assertThat(mSystemBackupAgent.mAddedHelpersKey) .containsExactly( "account_sync_settings", "notifications", @@ -134,7 +142,7 @@ public class SystemBackupAgentTest { mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0); - assertThat(mSystemBackupAgent.mAddedHelpers) + assertThat(mSystemBackupAgent.mAddedHelpersKey) .containsExactly( "account_sync_settings", "preferred_activities", @@ -147,12 +155,42 @@ public class SystemBackupAgentTest { "companion"); } + @Test + public void onAddHelperIfEligibleForUser_flagIsOff_helpersHaveNoLogger() { + UserHandle userHandle = new UserHandle(UserHandle.USER_SYSTEM); + when(mUserManagerMock.isProfile()).thenReturn(false); + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_METRICS_SYSTEM_BACKUP_AGENTS); + + mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0); + + for (BackupHelperWithLogger helper:mSystemBackupAgent.mAddedHelpers){ + assertThat(helper.isLoggerSet()).isFalse(); + } + } + + @Test + public void onAddHelperIfEligibleForUser_flagIsOn_helpersHaveLogger() { + UserHandle userHandle = new UserHandle(UserHandle.USER_SYSTEM); + when(mUserManagerMock.isProfile()).thenReturn(false); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_METRICS_SYSTEM_BACKUP_AGENTS); + + mSystemBackupAgent.onCreate(userHandle, /* backupDestination= */ 0); + + for (BackupHelperWithLogger helper:mSystemBackupAgent.mAddedHelpers){ + assertThat(helper.isLoggerSet()).isTrue(); + } + } + private class TestableSystemBackupAgent extends SystemBackupAgent { - final Set<String> mAddedHelpers = new ArraySet<>(); + final Set<String> mAddedHelpersKey = new ArraySet<>(); + final Set<BackupHelperWithLogger> mAddedHelpers = new ArraySet<>(); @Override public void addHelper(String keyPrefix, BackupHelper helper) { - mAddedHelpers.add(keyPrefix); + mAddedHelpersKey.add(keyPrefix); + if (helper instanceof BackupHelperWithLogger) { + mAddedHelpers.add((BackupHelperWithLogger) helper); + } } @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index ec7e35982311..a65ef00f8a21 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -367,7 +367,7 @@ public class PackageArchiverTest { verify(mInstallerService).uninstall( eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)), eq(CALLER_PACKAGE), eq(DELETE_ARCHIVE | DELETE_KEEP_DATA), eq(mIntentSender), - eq(UserHandle.CURRENT.getIdentifier()), anyInt()); + eq(UserHandle.CURRENT.getIdentifier()), anyInt(), anyInt()); ArchiveState expectedArchiveState = createArchiveState(); ArchiveState actualArchiveState = mPackageSetting.readUserState( @@ -391,7 +391,7 @@ public class PackageArchiverTest { eq(CALLER_PACKAGE), eq(DELETE_ARCHIVE | DELETE_KEEP_DATA), eq(mIntentSender), - eq(UserHandle.CURRENT.getIdentifier()), anyInt()); + eq(UserHandle.CURRENT.getIdentifier()), anyInt(), anyInt()); ArchiveState expectedArchiveState = createArchiveState(); ArchiveState actualArchiveState = mPackageSetting.readUserState( diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 57c3a1d5f364..95cfc2a304d4 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -22,6 +22,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.view.accessibility.Flags.FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG; +import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES; import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; @@ -82,6 +83,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import com.android.compatibility.common.util.TestUtils; +import com.android.internal.R; import com.android.internal.compat.IPlatformCompat; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; @@ -857,8 +859,7 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_requiredByDefault() { mockManageAccessibilityGranted(mTestableContext); - final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); - info.setComponentName(COMPONENT_NAME); + final AccessibilityServiceInfo info = mockAccessibilityServiceInfo(COMPONENT_NAME); assertThat(mA11yms.isAccessibilityServiceWarningRequired(info)).isTrue(); } @@ -867,10 +868,9 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_notRequiredIfAlreadyEnabled() { mockManageAccessibilityGranted(mTestableContext); - final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo(); - info_a.setComponentName(COMPONENT_NAME); - final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo(); - info_b.setComponentName(new ComponentName("package_b", "class_b")); + final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(COMPONENT_NAME); + final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( + new ComponentName("package_b", "class_b")); final AccessibilityUserState userState = mA11yms.getCurrentUserState(); userState.mEnabledServices.clear(); userState.mEnabledServices.add(info_b.getComponentName()); @@ -883,12 +883,12 @@ public class AccessibilityManagerServiceTest { @RequiresFlagsEnabled(FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG) public void testIsAccessibilityServiceWarningRequired_notRequiredIfExistingShortcut() { mockManageAccessibilityGranted(mTestableContext); - final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo(); - info_a.setComponentName(new ComponentName("package_a", "class_a")); - final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo(); - info_b.setComponentName(new ComponentName("package_b", "class_b")); - final AccessibilityServiceInfo info_c = new AccessibilityServiceInfo(); - info_c.setComponentName(new ComponentName("package_c", "class_c")); + final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo( + new ComponentName("package_a", "class_a")); + final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( + new ComponentName("package_b", "class_b")); + final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo( + new ComponentName("package_c", "class_c")); final AccessibilityUserState userState = mA11yms.getCurrentUserState(); userState.mAccessibilityButtonTargets.clear(); userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString()); @@ -900,6 +900,51 @@ public class AccessibilityManagerServiceTest { assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse(); } + @Test + @RequiresFlagsEnabled({ + FLAG_CLEANUP_ACCESSIBILITY_WARNING_DIALOG, + FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES}) + public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() { + mockManageAccessibilityGranted(mTestableContext); + final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo( + new ComponentName("package_a", "class_a"), true); + final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo( + new ComponentName("package_b", "class_b"), false); + final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo( + new ComponentName("package_c", "class_c"), true); + mTestableContext.getOrCreateTestableResources().addOverride( + R.array.config_trustedAccessibilityServices, + new String[]{ + info_b.getComponentName().flattenToString(), + info_c.getComponentName().flattenToString()}); + + // info_a is not in the allowlist => require the warning + assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_a)).isTrue(); + // info_b is not preinstalled => require the warning + assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_b)).isTrue(); + // info_c is both in the allowlist and preinstalled => do not require the warning + assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse(); + } + + private static AccessibilityServiceInfo mockAccessibilityServiceInfo( + ComponentName componentName) { + return mockAccessibilityServiceInfo(componentName, false); + } + + private static AccessibilityServiceInfo mockAccessibilityServiceInfo( + ComponentName componentName, + boolean isSystemApp) { + AccessibilityServiceInfo accessibilityServiceInfo = + Mockito.spy(new AccessibilityServiceInfo()); + accessibilityServiceInfo.setComponentName(componentName); + ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class); + when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo); + mockResolveInfo.serviceInfo = Mockito.mock(ServiceInfo.class); + mockResolveInfo.serviceInfo.applicationInfo = Mockito.mock(ApplicationInfo.class); + when(mockResolveInfo.serviceInfo.applicationInfo.isSystemApp()).thenReturn(isSystemApp); + return accessibilityServiceInfo; + } + // Single package intents can trigger multiple PackageMonitor callbacks. // Collect the state of the lock in a set, since tests only care if calls // were all locked or all unlocked. diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 0973d46283ed..5e380108aeb3 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -25,6 +25,7 @@ import static com.android.server.hdmi.Constants.ADDR_RECORDER_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE; import static com.google.common.truth.Truth.assertThat; @@ -1807,4 +1808,35 @@ public class HdmiCecLocalDeviceTvTest { // TV should only send <Give Osd Name> once assertEquals(1, Collections.frequency(mNativeWrapper.getResultMessages(), giveOsdName)); } + + @Test + public void initiateCecByWakeupMessage_selectInternalSourceAfterDelay_broadcastsActiveSource() { + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE); + mTestLooper.dispatchAll(); + + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + mHdmiCecLocalDeviceTv.deviceSelect(ADDR_TV, new TestCallback()); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv); + } + + @Test + public void initiateCecByWakeupMessage_selectInternalSource_doesNotBroadcastActiveSource() { + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE); + mTestLooper.dispatchAll(); + + mHdmiCecLocalDeviceTv.deviceSelect(ADDR_TV, new TestCallback()); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); + } } 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 ba7b52e368f3..2a89b02482b3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1762,32 +1762,6 @@ public class ActivityRecordTests extends WindowTestsBase { assertEquals(1, task.getChildCount()); } - /** - * Test that an activity will not be destroyed if it is marked as non-destroyable. - */ - @Test - public void testSafelyDestroy_nonDestroyable() { - final ActivityRecord activity = createActivityWithTask(); - doReturn(false).when(activity).isDestroyable(); - - activity.safelyDestroy("test"); - - verify(activity, never()).destroyImmediately(anyString()); - } - - /** - * Test that an activity will not be destroyed if it is marked as non-destroyable. - */ - @Test - public void testSafelyDestroy_destroyable() { - final ActivityRecord activity = createActivityWithTask(); - doReturn(true).when(activity).isDestroyable(); - - activity.safelyDestroy("test"); - - verify(activity).destroyImmediately(anyString()); - } - @Test public void testRemoveImmediately() { final Consumer<Consumer<ActivityRecord>> test = setup -> { diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 402cbccbca01..c44be7b9db51 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -56,6 +56,7 @@ import android.os.Bundle; import android.os.RemoteCallback; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.util.ArraySet; import android.view.WindowManager; import android.window.BackAnimationAdapter; @@ -69,6 +70,7 @@ import android.window.TaskSnapshot; import android.window.WindowOnBackInvokedDispatcher; import com.android.server.LocalServices; +import com.android.window.flags.Flags; import org.junit.Before; import org.junit.Test; @@ -81,6 +83,12 @@ import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +/** + * Tests for the {@link BackNavigationController} class. + * + * Build/Install/Run: + * atest WmTests:BackNavigationControllerTests + */ @Presubmit @RunWith(WindowTestRunner.class) public class BackNavigationControllerTests extends WindowTestsBase { @@ -623,6 +631,22 @@ public class BackNavigationControllerTests extends WindowTestsBase { 0, navigationObserver.getCount()); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG) + public void testAdjacentFocusInActivityEmbedding() { + Task task = createTask(mDefaultDisplay); + TaskFragment primary = createTaskFragmentWithActivity(task); + TaskFragment secondary = createTaskFragmentWithActivity(task); + primary.setAdjacentTaskFragment(secondary); + secondary.setAdjacentTaskFragment(primary); + + WindowState windowState = mock(WindowState.class); + doReturn(windowState).when(mWm).getFocusedWindowLocked(); + doReturn(primary).when(windowState).getTaskFragment(); + + startBackNavigation(); + verify(mWm).moveFocusToAdjacentWindow(any(), anyInt()); + } /** * Test with 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 782d89cdcd29..95850ac2f3b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2131,8 +2131,8 @@ public class DisplayContentTests extends WindowTestsBase { // Once transition starts, rotation is applied and transition shows DC rotating. testPlayer.startTransition(); waitUntilHandlersIdle(); - verify(activity1).ensureActivityConfiguration(anyBoolean(), anyBoolean()); - verify(activity2).ensureActivityConfiguration(anyBoolean(), anyBoolean()); + verify(activity1).ensureActivityConfiguration(anyBoolean()); + verify(activity2).ensureActivityConfiguration(anyBoolean()); assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation()); assertNotNull(testPlayer.mLastReady); assertTrue(testPlayer.mController.isPlaying()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index be96e60917a3..9e00f927a568 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -283,12 +283,12 @@ public class DisplayPolicyTests extends WindowTestsBase { policy.screenTurnedOff(); policy.setAwake(false); - policy.screenTurnedOn(null /* screenOnListener */); + policy.screenTurningOn(null /* screenOnListener */); assertTrue(wpc.isShowingUiWhileDozing()); policy.screenTurnedOff(); assertFalse(wpc.isShowingUiWhileDozing()); - policy.screenTurnedOn(null /* screenOnListener */); + policy.screenTurningOn(null /* screenOnListener */); assertTrue(wpc.isShowingUiWhileDozing()); policy.setAwake(true); assertFalse(wpc.isShowingUiWhileDozing()); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 5518c604446d..6759eef2066d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -197,10 +197,10 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); translucentActivity.setState(DESTROYED, "testing"); @@ -225,10 +225,10 @@ public class SizeCompatTests extends WindowTestsBase { // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); spyOn(translucentActivity.mLetterboxUiController); @@ -300,10 +300,10 @@ public class SizeCompatTests extends WindowTestsBase { // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); spyOn(translucentActivity.mLetterboxUiController); @@ -376,10 +376,10 @@ public class SizeCompatTests extends WindowTestsBase { // Launch translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); // Transparent strategy applied assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); @@ -404,10 +404,10 @@ public class SizeCompatTests extends WindowTestsBase { // Launch translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); // Transparent strategy applied assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); @@ -441,10 +441,10 @@ public class SizeCompatTests extends WindowTestsBase { // Launch translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); // Transparent strategy applied assertTrue(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); @@ -465,12 +465,12 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) .setMinAspectRatio(1.1f) .setMaxAspectRatio(3f) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); // We check bounds final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); @@ -493,9 +493,9 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .build(); - doReturn(false).when(translucentActivity).fillsParent(); final Configuration requestedConfig = translucentActivity.getRequestedOverrideConfiguration(); final WindowConfiguration translucentWinConf = requestedConfig.windowConfiguration; @@ -525,12 +525,12 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) .setMinAspectRatio(1.1f) .setMaxAspectRatio(3f) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); // We check bounds final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); @@ -538,10 +538,10 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(opaqueBounds, translucentRequestedBounds); // Launch another translucent activity final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) .build(); - doReturn(false).when(translucentActivity2).fillsParent(); mTask.addChild(translucentActivity2); // We check bounds final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds(); @@ -558,9 +558,9 @@ public class SizeCompatTests extends WindowTestsBase { // simplicity. doReturn(true).when(mActivity).isEmbedded(); // Translucent Activity - final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).build(); + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent).build(); doReturn(false).when(translucentActivity).matchParentBounds(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); // Check the strategy has not being applied assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); @@ -580,10 +580,10 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.inSizeCompatMode()); // We launch a transparent activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); // It should not be in SCM assertFalse(translucentActivity.inSizeCompatMode()); @@ -600,12 +600,16 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Translucent Activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .build(); - doReturn(false).when(translucentActivity).fillsParent(); - spyOn(mActivity); + assertFalse(translucentActivity.fillsParent()); + assertTrue(mActivity.fillsParent()); + mActivity.finishing = true; + assertFalse(mActivity.occludesParent()); mTask.addChild(translucentActivity); - verify(mActivity).isFinishing(); + // The translucent activity won't inherit letterbox behavior from a finishing activity. + assertFalse(translucentActivity.mLetterboxUiController.hasInheritedLetterboxBehavior()); } @Test @@ -619,10 +623,10 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); // We launch a transparent activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); @@ -655,10 +659,10 @@ public class SizeCompatTests extends WindowTestsBase { // We launch a transparent activity final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setActivityTheme(android.R.style.Theme_Translucent) .setLaunchedFromUid(mActivity.getUid()) .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) .build(); - doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); // The transparent activity inherits the compat display insets of the opaque activity @@ -1020,8 +1024,17 @@ public class SizeCompatTests extends WindowTestsBase { // Activity is sandboxed due to fixed aspect ratio. assertActivityMaxBoundsSandboxed(); + // Prepare the states for verifying relaunching after changing orientation. + mActivity.finishRelaunching(); + mActivity.setState(RESUMED, "testFixedAspectRatioOrientationChangeOrientation"); + mActivity.setLastReportedConfiguration(mAtm.getGlobalConfiguration(), + mActivity.getConfiguration()); + // Change the fixed orientation. mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); + assertTrue(mActivity.isRelaunching()); + assertTrue(mActivity.mLetterboxUiController + .getIsRelaunchingAfterRequestedOrientationChanged()); assertFitted(); assertEquals(originalBounds.width(), mActivity.getBounds().height()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index 45ecc3f762ec..00ecd008cde7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -215,7 +215,7 @@ class TestDisplayContent extends DisplayContent { doReturn(false).when(newDisplay).supportsSystemDecorations(); } // Update the display policy to make the screen fully turned on so animation is allowed - displayPolicy.screenTurnedOn(null /* screenOnListener */); + displayPolicy.screenTurningOn(null /* screenOnListener */); displayPolicy.finishKeyguardDrawn(); displayPolicy.finishWindowsDrawn(); displayPolicy.finishScreenTurningOn(); 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 c7c791337bb4..a0bafb64090f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -229,7 +229,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); // Update the display policy to make the screen fully turned on so animation is allowed final DisplayPolicy displayPolicy = mDefaultDisplay.getDisplayPolicy(); - displayPolicy.screenTurnedOn(null /* screenOnListener */); + displayPolicy.screenTurningOn(null /* screenOnListener */); displayPolicy.finishKeyguardDrawn(); displayPolicy.finishWindowsDrawn(); displayPolicy.finishScreenTurningOn(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index b22d8ac3b48f..cbd552454642 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13148,7 +13148,7 @@ public class TelephonyManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public ServiceState getServiceStateForSubscriber(int subId) { - return getServiceStateForSubscriber(getSubId(), false, false); + return getServiceStateForSubscriber(subId, false, false); } /** |