diff options
252 files changed, 6383 insertions, 2233 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 16907b3432ec..b753aab33dcd 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -19,6 +19,7 @@ java_defaults { // Add java_aconfig_libraries to here to add them to the core framework srcs: [ ":com.android.hardware.camera2-aconfig-java{.generated_srcjars}", + ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", ], } @@ -30,6 +31,7 @@ java_defaults { libs: ["fake_device_config"], } +// Camera aconfig_declarations { name: "com.android.hardware.camera2-aconfig", package: "com.android.hardware.camera2", @@ -41,3 +43,16 @@ java_aconfig_library { aconfig_declarations: "com.android.hardware.camera2-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Window +aconfig_declarations { + name: "com.android.window.flags.window-aconfig", + package: "com.android.window.flags", + srcs: ["core/java/android/window/flags/*.aconfig"], +} + +java_aconfig_library { + name: "com.android.window.flags.window-aconfig-java", + aconfig_declarations: "com.android.window.flags.window-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 53554bce43b8..431f0b9317ac 100644 --- a/Android.bp +++ b/Android.bp @@ -408,21 +408,18 @@ java_defaults { ], } -java_library { - name: "framework-minus-apex", +// Separated so framework-minus-apex-defaults can be used without the libs dependency +java_defaults { + name: "framework-minus-apex-with-libs-defaults", defaults: ["framework-minus-apex-defaults"], - installable: true, - // For backwards compatibility. - stem: "framework", - apex_available: ["//apex_available:platform"], - visibility: [ - "//frameworks/base", - // TODO(b/147128803) remove the below lines - "//frameworks/base/apex/blobstore/framework", - "//frameworks/base/apex/jobscheduler/framework", - "//frameworks/base/packages/Tethering/tests/unit", - "//packages/modules/Connectivity/Tethering/tests/unit", + libs: [ + "framework-virtualization.stubs.module_lib", + "framework-location.impl", ], +} + +java_defaults { + name: "framework-non-updatable-lint-defaults", lint: { extra_check_modules: ["AndroidFrameworkLintChecker"], disabled_checks: ["ApiMightLeakAppVisibility"], @@ -436,6 +433,43 @@ java_library { "UseOfCallerAwareMethodsWithClearedIdentity", ], }, +} + +// we are unfortunately building the turbine jar twice, but more efficient and less complex +// than generating a similar set of stubs with metalava +java_library { + name: "framework-minus-apex-headers", + defaults: ["framework-minus-apex-defaults"], + installable: false, + // For backwards compatibility. + stem: "framework", + apex_available: ["//apex_available:platform"], + visibility: [ + "//frameworks/base/location", + ], + compile_dex: false, + headers_only: true, +} + +java_library { + name: "framework-minus-apex", + defaults: [ + "framework-minus-apex-with-libs-defaults", + "framework-non-updatable-lint-defaults", + ], + installable: true, + // For backwards compatibility. + stem: "framework", + apex_available: ["//apex_available:platform"], + visibility: [ + "//frameworks/base", + "//frameworks/base/location", + // TODO(b/147128803) remove the below lines + "//frameworks/base/apex/blobstore/framework", + "//frameworks/base/apex/jobscheduler/framework", + "//frameworks/base/packages/Tethering/tests/unit", + "//packages/modules/Connectivity/Tethering/tests/unit", + ], errorprone: { javacflags: [ "-Xep:AndroidFrameworkCompatChange:ERROR", @@ -446,7 +480,7 @@ java_library { java_library { name: "framework-minus-apex-intdefs", - defaults: ["framework-minus-apex-defaults"], + defaults: ["framework-minus-apex-with-libs-defaults"], plugins: ["intdef-annotation-processor"], // Errorprone and android lint will already run on framework-minus-apex, don't rerun them on @@ -474,6 +508,7 @@ java_library { installable: false, // this lib is a build-only library static_libs: [ "app-compat-annotations", + "framework-location.impl", "framework-minus-apex", "framework-updatable-stubs-module_libs_api", ], @@ -701,6 +736,97 @@ stubs_defaults { ], } +// Defaults for the java_sdk_libraries of unbundled jars from framework. +// java_sdk_libraries using these defaults should also add themselves to the +// non_updatable_modules list in frameworks/base/api/api.go +java_defaults { + name: "framework-non-updatable-unbundled-defaults", + defaults: ["framework-non-updatable-lint-defaults"], + + sdk_version: "core_platform", + + // Api scope settings + public: { + enabled: true, + sdk_version: "module_current", + libs: ["android_module_lib_stubs_current"], + }, + system: { + enabled: true, + sdk_version: "module_current", + libs: ["android_module_lib_stubs_current"], + }, + module_lib: { + enabled: true, + sdk_version: "module_current", + libs: ["android_module_lib_stubs_current"], + }, + test: { + enabled: true, + sdk_version: "test_frameworks_core_current", + libs: ["android_test_frameworks_core_stubs_current"], + }, + + stub_only_libs: [ + "framework-protos", + ], + impl_only_libs: [ + "framework-minus-apex-headers", // full access to framework-minus-apex including hidden API + "framework-annotations-lib", + ], + visibility: ["//visibility:public"], + stubs_library_visibility: ["//visibility:public"], + stubs_source_visibility: ["//visibility:private"], + impl_library_visibility: [ + ":__pkg__", + "//frameworks/base", + "//frameworks/base/api", // For framework-all + ], + defaults_visibility: [ + "//frameworks/base/location", + ], + plugins: [ + "error_prone_android_framework", + ], + errorprone: { + javacflags: [ + "-Xep:AndroidFrameworkCompatChange:ERROR", + "-Xep:AndroidFrameworkUid:ERROR", + ], + }, + + // Include manual annotations in API txt files + merge_annotations_dirs: ["metalava-manual"], + + // Use the source of annotations that affect metalava doc generation, since + // the relevant generation instructions are themselves in javadoc, which is + // not present in class files. + api_srcs: [":framework-metalava-annotations"], + + // Framework modules are not generally shared libraries, i.e. they are not + // intended, and must not be allowed, to be used in a <uses-library> manifest + // entry. + shared_library: false, + + // Prevent dependencies that do not specify an sdk_version from accessing the + // implementation library by default and force them to use stubs instead. + default_to_stubs: true, + + // Subdirectory for the artifacts that are copied to the dist directory + dist_group: "android", + + droiddoc_options: [ + "--error UnhiddenSystemApi " + + "--hide CallbackInterface " + + "--hide HiddenTypedefConstant " + + "--hide RequiresPermission " + + "--enhance-documentation " + + "--hide-package com.android.server ", + ], + + annotations_enabled: true, +} + build = [ "AconfigFlags.bp", "ProtoLibraries.bp", diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index 098b2fb67360..d48d84ba6980 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -1118,6 +1118,7 @@ public final class JobStore { } boolean needFileMigration = false; long nowElapsed = sElapsedRealtimeClock.millis(); + int numDuplicates = 0; synchronized (mLock) { for (File file : files) { final AtomicFile aFile = createJobFile(file); @@ -1126,6 +1127,16 @@ public final class JobStore { if (jobs != null) { for (int i = 0; i < jobs.size(); i++) { JobStatus js = jobs.get(i); + final JobStatus existingJob = this.jobSet.get( + js.getUid(), js.getNamespace(), js.getJobId()); + if (existingJob != null) { + numDuplicates++; + // Jobs are meant to have unique uid-namespace-jobId + // combinations, but we've somehow read multiple jobs with the + // combination. Drop the latter one since keeping both will + // result in other issues. + continue; + } js.prepareLocked(); js.enqueueTime = nowElapsed; this.jobSet.add(js); @@ -1174,6 +1185,10 @@ public final class JobStore { migrateJobFilesAsync(); } + if (numDuplicates > 0) { + Slog.wtf(TAG, "Encountered " + numDuplicates + " duplicate persisted jobs"); + } + // Log the count immediately after loading from boot. mCurrentJobSetSize = numJobs; mScheduledJob30MinHighWaterMark = mCurrentJobSetSize; diff --git a/api/Android.bp b/api/Android.bp index e9cc40513221..6986ac09f89e 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -87,6 +87,7 @@ combined_apis { "framework-devicelock", "framework-graphics", "framework-healthfitness", + "framework-location", "framework-media", "framework-mediaprovider", "framework-ondevicepersonalization", diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index c37ff9761757..e481fe9971fc 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -487,7 +487,6 @@ java_library { static_libs: [ "all-updatable-modules-system-stubs", "android-non-updatable.stubs.test", - "private-stub-annotations-jar", ], defaults: [ "android.jar_defaults", @@ -613,6 +612,7 @@ java_defaults { api_contributions: [ "test-api-stubs-docs-non-updatable.api.contribution", "framework-virtualization.stubs.source.test.api.contribution", + "framework-location.stubs.source.test.api.contribution", ], } diff --git a/api/api.go b/api/api.go index d5c6145ba062..6095a9a781d8 100644 --- a/api/api.go +++ b/api/api.go @@ -31,6 +31,7 @@ const art = "art.module.public.api" const conscrypt = "conscrypt.module.public.api" const i18n = "i18n.module.public.api" const virtualization = "framework-virtualization" +const location = "framework-location" var core_libraries_modules = []string{art, conscrypt, i18n} @@ -42,7 +43,7 @@ var core_libraries_modules = []string{art, conscrypt, i18n} // APIs. // In addition, the modules in this list are allowed to contribute to test APIs // stubs. -var non_updatable_modules = []string{virtualization} +var non_updatable_modules = []string{virtualization, location} // The intention behind this soong plugin is to generate a number of "merged" // API-related modules that would otherwise require a large amount of very @@ -278,8 +279,10 @@ func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) { } func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) { - // The user of this module compiles against the "core" SDK, so remove core libraries to avoid dupes. + // The user of this module compiles against the "core" SDK and against non-updatable modules, + // so remove to avoid dupes. modules = removeAll(modules, core_libraries_modules) + modules = removeAll(modules, non_updatable_modules) props := libraryProps{} props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api") props.Static_libs = transformArray(modules, "", ".stubs.module_lib") diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index b6dc32a29f04..9dedf7025143 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -192,7 +192,7 @@ public class Bmgr { } if ("whitelist".equals(op)) { - doPrintWhitelist(); + doPrintAllowlist(); return; } @@ -911,7 +911,7 @@ public class Bmgr { } } - private void doPrintWhitelist() { + private void doPrintAllowlist() { try { final String[] whitelist = mBmgr.getTransportWhitelist(); if (whitelist != null) { diff --git a/core/api/current.txt b/core/api/current.txt index 3cb7468c1444..cb929261bde1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14341,14 +14341,14 @@ package android.database.sqlite { method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory); method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams); method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String); - method public int delete(String, String, String[]); + method public int delete(@NonNull String, @Nullable String, @Nullable String[]); method public static boolean deleteDatabase(@NonNull java.io.File); method public void disableWriteAheadLogging(); method public boolean enableWriteAheadLogging(); method public void endTransaction(); method public void execPerConnectionSQL(@NonNull String, @Nullable Object[]) throws android.database.SQLException; method public void execSQL(String) throws android.database.SQLException; - method public void execSQL(String, Object[]) throws android.database.SQLException; + method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException; method public static String findEditTable(String); method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs(); method public long getLastChangedRowCount(); @@ -14360,9 +14360,9 @@ package android.database.sqlite { method public long getTotalChangedRowCount(); method public int getVersion(); method public boolean inTransaction(); - method public long insert(String, String, android.content.ContentValues); - method public long insertOrThrow(String, String, android.content.ContentValues) throws android.database.SQLException; - method public long insertWithOnConflict(String, String, android.content.ContentValues, int); + method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues); + method public long insertOrThrow(@NonNull String, @Nullable String, @Nullable android.content.ContentValues) throws android.database.SQLException; + method public long insertWithOnConflict(@NonNull String, @Nullable String, @Nullable android.content.ContentValues, int); method public boolean isDatabaseIntegrityOk(); method public boolean isDbLockedByCurrentThread(); method @Deprecated public boolean isDbLockedByOtherThreads(); @@ -14379,19 +14379,19 @@ package android.database.sqlite { method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(@NonNull java.io.File, @Nullable android.database.sqlite.SQLiteDatabase.CursorFactory); method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(@NonNull String, @Nullable android.database.sqlite.SQLiteDatabase.CursorFactory); method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(@NonNull String, @Nullable android.database.sqlite.SQLiteDatabase.CursorFactory, @Nullable android.database.DatabaseErrorHandler); - method public android.database.Cursor query(boolean, String, String[], String, String[], String, String, String, String); - method public android.database.Cursor query(boolean, String, String[], String, String[], String, String, String, String, android.os.CancellationSignal); - method public android.database.Cursor query(String, String[], String, String[], String, String, String); - method public android.database.Cursor query(String, String[], String, String[], String, String, String, String); - method public android.database.Cursor queryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, String, String[], String, String[], String, String, String, String); - method public android.database.Cursor queryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, String, String[], String, String[], String, String, String, String, android.os.CancellationSignal); - method public android.database.Cursor rawQuery(String, String[]); - method public android.database.Cursor rawQuery(String, String[], android.os.CancellationSignal); - method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, String, String[], String); - method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, String, String[], String, android.os.CancellationSignal); + method @NonNull public android.database.Cursor query(boolean, @NonNull String, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable String, @Nullable String, @Nullable String); + method @NonNull public android.database.Cursor query(boolean, @NonNull String, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable String, @Nullable String, @Nullable String, @Nullable android.os.CancellationSignal); + method @NonNull public android.database.Cursor query(@NonNull String, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable String, @Nullable String); + method @NonNull public android.database.Cursor query(@NonNull String, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable String, @Nullable String, @Nullable String); + method @NonNull public android.database.Cursor queryWithFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, @NonNull String, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable String, @Nullable String, @Nullable String); + method @NonNull public android.database.Cursor queryWithFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory, boolean, @NonNull String, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable String, @Nullable String, @Nullable String, @Nullable android.os.CancellationSignal); + method @NonNull public android.database.Cursor rawQuery(@NonNull String, @Nullable String[]); + method @NonNull public android.database.Cursor rawQuery(@NonNull String, @Nullable String[], @Nullable android.os.CancellationSignal); + method @NonNull public android.database.Cursor rawQueryWithFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory, @NonNull String, @Nullable String[], @NonNull String); + method @NonNull public android.database.Cursor rawQueryWithFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory, @NonNull String, @Nullable String[], @NonNull String, @Nullable android.os.CancellationSignal); method public static int releaseMemory(); - method public long replace(String, String, android.content.ContentValues); - method public long replaceOrThrow(String, String, android.content.ContentValues) throws android.database.SQLException; + method public long replace(@NonNull String, @Nullable String, @Nullable android.content.ContentValues); + method public long replaceOrThrow(@NonNull String, @Nullable String, @Nullable android.content.ContentValues) throws android.database.SQLException; method public void setCustomAggregateFunction(@NonNull String, @NonNull java.util.function.BinaryOperator<java.lang.String>) throws android.database.sqlite.SQLiteException; method public void setCustomScalarFunction(@NonNull String, @NonNull java.util.function.UnaryOperator<java.lang.String>) throws android.database.sqlite.SQLiteException; method public void setForeignKeyConstraintsEnabled(boolean); @@ -14402,8 +14402,8 @@ package android.database.sqlite { method public void setPageSize(long); method public void setTransactionSuccessful(); method public void setVersion(int); - method public int update(String, android.content.ContentValues, String, String[]); - method public int updateWithOnConflict(String, android.content.ContentValues, String, String[], int); + method public int update(@NonNull String, @Nullable android.content.ContentValues, @Nullable String, @Nullable String[]); + method public int updateWithOnConflict(@NonNull String, @Nullable android.content.ContentValues, @Nullable String, @Nullable String[], int); method public void validateSql(@NonNull String, @Nullable android.os.CancellationSignal); method @Deprecated public boolean yieldIfContended(); method public boolean yieldIfContendedSafely(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 61415839ea2c..2b3e8e9e0a92 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -301,6 +301,7 @@ package android { field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE"; field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER"; field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER"; + field public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE"; field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION"; field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM"; field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER"; @@ -17233,9 +17234,8 @@ package android.telephony.satellite { method public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); } - public class SatelliteManager { + public final class SatelliteManager { method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void onDeviceAlignedWithSatellite(boolean); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); @@ -17250,6 +17250,7 @@ package android.telephony.satellite { method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e57849957f13..fbb97ffea035 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -243,6 +243,7 @@ import android.view.translation.ITranslationManager; import android.view.translation.TranslationManager; import android.view.translation.UiTranslationManager; +import com.android.internal.R; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.internal.app.ISoundTriggerService; @@ -871,6 +872,10 @@ public final class SystemServiceRegistry { PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) { return null; } + if (!ctx.getResources().getBoolean(R.bool.config_enableVirtualDeviceManager)) { + return null; + } + IVirtualDeviceManager service = IVirtualDeviceManager.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.VIRTUAL_DEVICE_SERVICE)); return new VirtualDeviceManager(service, ctx.getOuterContext()); diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 3927b40fdc74..3abe1d9494f0 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -24,6 +24,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UiThread; import android.annotation.UserIdInt; import android.app.IServiceConnection; import android.app.PendingIntent; @@ -39,6 +40,10 @@ import android.content.pm.ShortcutInfo; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.util.DisplayMetrics; @@ -53,6 +58,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; /** * Updates AppWidget state; gets information about installed AppWidget providers and other @@ -475,6 +481,8 @@ public class AppWidgetManager { private static final String TAG = "AppWidgetManager"; + private static Executor sUpdateExecutor; + /** * An intent extra that contains multiple appWidgetIds. These are id values as * they were provided to the application during a recent restore from backup. It is @@ -510,6 +518,8 @@ public class AppWidgetManager { private final IAppWidgetService mService; private final DisplayMetrics mDisplayMetrics; + private boolean mHasPostedLegacyLists = false; + /** * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context * Context} object. @@ -552,6 +562,13 @@ public class AppWidgetManager { }); } + private boolean isPostingTaskToBackground(@Nullable RemoteViews views) { + return Looper.myLooper() == Looper.getMainLooper() + && RemoteViews.isAdapterConversionEnabled() + && (mHasPostedLegacyLists = mHasPostedLegacyLists + || (views != null && views.hasLegacyLists())); + } + /** * Set the RemoteViews to use for the specified appWidgetIds. * <p> @@ -575,6 +592,19 @@ public class AppWidgetManager { if (mService == null) { return; } + + if (isPostingTaskToBackground(views)) { + createUpdateExecutorIfNull().execute(() -> { + try { + mService.updateAppWidgetIds(mPackageName, appWidgetIds, views); + } catch (RemoteException e) { + Log.e(TAG, "Error updating app widget views in background", e); + } + }); + + return; + } + try { mService.updateAppWidgetIds(mPackageName, appWidgetIds, views); } catch (RemoteException e) { @@ -683,6 +713,19 @@ public class AppWidgetManager { if (mService == null) { return; } + + if (isPostingTaskToBackground(views)) { + createUpdateExecutorIfNull().execute(() -> { + try { + mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views); + } catch (RemoteException e) { + Log.e(TAG, "Error partially updating app widget views in background", e); + } + }); + + return; + } + try { mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views); } catch (RemoteException e) { @@ -738,6 +781,19 @@ public class AppWidgetManager { if (mService == null) { return; } + + if (isPostingTaskToBackground(views)) { + createUpdateExecutorIfNull().execute(() -> { + try { + mService.updateAppWidgetProvider(provider, views); + } catch (RemoteException e) { + Log.e(TAG, "Error updating app widget view using provider in background", e); + } + }); + + return; + } + try { mService.updateAppWidgetProvider(provider, views); } catch (RemoteException e) { @@ -797,28 +853,45 @@ public class AppWidgetManager { if (mService == null) { return; } + + if (!RemoteViews.isAdapterConversionEnabled()) { + try { + mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + + return; + } + + if (Looper.myLooper() == Looper.getMainLooper()) { + mHasPostedLegacyLists = true; + createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds, + viewId)); + } else { + notifyCollectionWidgetChange(appWidgetIds, viewId); + } + } + + private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) { try { - if (RemoteViews.isAdapterConversionEnabled()) { - List<CompletableFuture<Void>> updateFutures = new ArrayList<>(); - for (int i = 0; i < appWidgetIds.length; i++) { - final int widgetId = appWidgetIds[i]; - updateFutures.add(CompletableFuture.runAsync(() -> { - try { - RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId); - if (views.replaceRemoteCollections(viewId)) { - updateAppWidget(widgetId, views); - } - } catch (Exception e) { - Log.e(TAG, "Error notifying changes in RemoteViews", e); + List<CompletableFuture<Void>> updateFutures = new ArrayList<>(); + for (int i = 0; i < appWidgetIds.length; i++) { + final int widgetId = appWidgetIds[i]; + updateFutures.add(CompletableFuture.runAsync(() -> { + try { + RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId); + if (views.replaceRemoteCollections(viewId)) { + updateAppWidget(widgetId, views); } - })); - } - CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join(); - } else { - mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId); + } catch (Exception e) { + Log.e(TAG, "Error notifying changes in RemoteViews", e); + } + })); } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join(); + } catch (Exception e) { + Log.e(TAG, "Error notifying changes for all widgets", e); } } @@ -1360,4 +1433,20 @@ public class AppWidgetManager { throw e.rethrowFromSystemServer(); } } + + @UiThread + private static @NonNull Executor createUpdateExecutorIfNull() { + if (sUpdateExecutor == null) { + sUpdateExecutor = new HandlerExecutor(createAndStartNewHandler( + "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND)); + } + + return sUpdateExecutor; + } + + private static @NonNull Handler createAndStartNewHandler(@NonNull String name, int priority) { + HandlerThread thread = new HandlerThread(name, priority); + thread.start(); + return thread.getThreadHandler(); + } } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 5b80e6a0b188..746f2f23fd5c 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -44,10 +44,12 @@ import android.util.EventLog; import android.util.Log; import android.util.Pair; import android.util.Printer; + import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.NeverCompile; import dalvik.system.CloseGuard; + import java.io.File; import java.io.FileFilter; import java.io.IOException; @@ -1473,8 +1475,10 @@ public final class SQLiteDatabase extends SQLiteClosable { * SQL WHERE clause (excluding the WHERE itself). Passing null * will return all rows for the given table. * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they + * replaced by the values from selectionArgs, in the order that they * appear in the selection. The values will be bound as Strings. + * If selection is null or does not contain ?s then selectionArgs + * may be null. * @param groupBy A filter declaring how to group rows, formatted as an SQL * GROUP BY clause (excluding the GROUP BY itself). Passing null * will cause the rows to not be grouped. @@ -1492,9 +1496,11 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ - public Cursor query(boolean distinct, String table, String[] columns, - String selection, String[] selectionArgs, String groupBy, - String having, String orderBy, String limit) { + @NonNull + public Cursor query(boolean distinct, @NonNull String table, + @Nullable String[] columns, @Nullable String selection, + @Nullable String[] selectionArgs, @Nullable String groupBy, @Nullable String having, + @Nullable String orderBy, @Nullable String limit) { return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit, null); } @@ -1511,8 +1517,10 @@ public final class SQLiteDatabase extends SQLiteClosable { * SQL WHERE clause (excluding the WHERE itself). Passing null * will return all rows for the given table. * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they + * replaced by the values from selectionArgs, in the order that they * appear in the selection. The values will be bound as Strings. + * If selection is null or does not contain ?s then selectionArgs + * may be null. * @param groupBy A filter declaring how to group rows, formatted as an SQL * GROUP BY clause (excluding the GROUP BY itself). Passing null * will cause the rows to not be grouped. @@ -1533,9 +1541,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ - public Cursor query(boolean distinct, String table, String[] columns, - String selection, String[] selectionArgs, String groupBy, - String having, String orderBy, String limit, CancellationSignal cancellationSignal) { + @NonNull + public Cursor query(boolean distinct, @NonNull String table, + @Nullable String[] columns, @Nullable String selection, + @Nullable String[] selectionArgs, @Nullable String groupBy, @Nullable String having, + @Nullable String orderBy, @Nullable String limit, + @Nullable CancellationSignal cancellationSignal) { return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit, cancellationSignal); } @@ -1553,8 +1564,10 @@ public final class SQLiteDatabase extends SQLiteClosable { * SQL WHERE clause (excluding the WHERE itself). Passing null * will return all rows for the given table. * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they + * replaced by the values from selectionArgs, in the order that they * appear in the selection. The values will be bound as Strings. + * If selection is null or does not contain ?s then selectionArgs + * may be null. * @param groupBy A filter declaring how to group rows, formatted as an SQL * GROUP BY clause (excluding the GROUP BY itself). Passing null * will cause the rows to not be grouped. @@ -1572,10 +1585,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ - public Cursor queryWithFactory(CursorFactory cursorFactory, - boolean distinct, String table, String[] columns, - String selection, String[] selectionArgs, String groupBy, - String having, String orderBy, String limit) { + @SuppressLint("SamShouldBeLast") + @NonNull + public Cursor queryWithFactory(@Nullable CursorFactory cursorFactory, + boolean distinct, @NonNull String table, @Nullable String[] columns, + @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String groupBy, + @Nullable String having, @Nullable String orderBy, @Nullable String limit) { return queryWithFactory(cursorFactory, distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit, null); } @@ -1593,8 +1608,10 @@ public final class SQLiteDatabase extends SQLiteClosable { * SQL WHERE clause (excluding the WHERE itself). Passing null * will return all rows for the given table. * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they + * replaced by the values from selectionArgs, in the order that they * appear in the selection. The values will be bound as Strings. + * If selection is null or does not contain ?s then selectionArgs + * may be null. * @param groupBy A filter declaring how to group rows, formatted as an SQL * GROUP BY clause (excluding the GROUP BY itself). Passing null * will cause the rows to not be grouped. @@ -1615,10 +1632,13 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ - public Cursor queryWithFactory(CursorFactory cursorFactory, - boolean distinct, String table, String[] columns, - String selection, String[] selectionArgs, String groupBy, - String having, String orderBy, String limit, CancellationSignal cancellationSignal) { + @SuppressLint("SamShouldBeLast") + @NonNull + public Cursor queryWithFactory(@Nullable CursorFactory cursorFactory, + boolean distinct, @NonNull String table, @Nullable String[] columns, + @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String groupBy, + @Nullable String having, @Nullable String orderBy, @Nullable String limit, + @Nullable CancellationSignal cancellationSignal) { acquireReference(); try { String sql = SQLiteQueryBuilder.buildQueryString( @@ -1642,8 +1662,10 @@ public final class SQLiteDatabase extends SQLiteClosable { * SQL WHERE clause (excluding the WHERE itself). Passing null * will return all rows for the given table. * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they + * replaced by the values from selectionArgs, in the order that they * appear in the selection. The values will be bound as Strings. + * If selection is null or does not contain ?s then selectionArgs + * may be null. * @param groupBy A filter declaring how to group rows, formatted as an SQL * GROUP BY clause (excluding the GROUP BY itself). Passing null * will cause the rows to not be grouped. @@ -1659,9 +1681,10 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ - public Cursor query(String table, String[] columns, String selection, - String[] selectionArgs, String groupBy, String having, - String orderBy) { + @NonNull + public Cursor query(@NonNull String table, @Nullable String[] columns, + @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String groupBy, + @Nullable String having, @Nullable String orderBy) { return query(false, table, columns, selection, selectionArgs, groupBy, having, orderBy, null /* limit */); @@ -1678,8 +1701,10 @@ public final class SQLiteDatabase extends SQLiteClosable { * SQL WHERE clause (excluding the WHERE itself). Passing null * will return all rows for the given table. * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they + * replaced by the values from selectionArgs, in the order that they * appear in the selection. The values will be bound as Strings. + * If selection is null or does not contain ?s then selectionArgs + * may be null. * @param groupBy A filter declaring how to group rows, formatted as an SQL * GROUP BY clause (excluding the GROUP BY itself). Passing null * will cause the rows to not be grouped. @@ -1697,9 +1722,10 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ - public Cursor query(String table, String[] columns, String selection, - String[] selectionArgs, String groupBy, String having, - String orderBy, String limit) { + @NonNull + public Cursor query(@NonNull String table, @Nullable String[] columns, + @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String groupBy, + @Nullable String having, @Nullable String orderBy, @Nullable String limit) { return query(false, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit); @@ -1711,11 +1737,13 @@ public final class SQLiteDatabase extends SQLiteClosable { * @param sql the SQL query. The SQL string must not be ; terminated * @param selectionArgs You may include ?s in where clause in the query, * which will be replaced by the values from selectionArgs. The - * values will be bound as Strings. + * values will be bound as Strings. If selection is null or does not contain ?s then + * selectionArgs may be null. * @return A {@link Cursor} object, which is positioned before the first entry. Note that * {@link Cursor}s are not synchronized, see the documentation for more details. */ - public Cursor rawQuery(String sql, String[] selectionArgs) { + @NonNull + public Cursor rawQuery(@NonNull String sql, @Nullable String[] selectionArgs) { return rawQueryWithFactory(null, sql, selectionArgs, null, null); } @@ -1725,15 +1753,17 @@ public final class SQLiteDatabase extends SQLiteClosable { * @param sql the SQL query. The SQL string must not be ; terminated * @param selectionArgs You may include ?s in where clause in the query, * which will be replaced by the values from selectionArgs. The - * values will be bound as Strings. + * values will be bound as Strings. If selection is null or does not contain ?s then + * selectionArgs may be null. * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. * @return A {@link Cursor} object, which is positioned before the first entry. Note that * {@link Cursor}s are not synchronized, see the documentation for more details. */ - public Cursor rawQuery(String sql, String[] selectionArgs, - CancellationSignal cancellationSignal) { + @NonNull + public Cursor rawQuery(@NonNull String sql, @Nullable String[] selectionArgs, + @Nullable CancellationSignal cancellationSignal) { return rawQueryWithFactory(null, sql, selectionArgs, null, cancellationSignal); } @@ -1744,14 +1774,16 @@ public final class SQLiteDatabase extends SQLiteClosable { * @param sql the SQL query. The SQL string must not be ; terminated * @param selectionArgs You may include ?s in where clause in the query, * which will be replaced by the values from selectionArgs. The - * values will be bound as Strings. + * values will be bound as Strings. If selection is null or does not contain ?s then + * selectionArgs may be null. * @param editTable the name of the first table, which is editable * @return A {@link Cursor} object, which is positioned before the first entry. Note that * {@link Cursor}s are not synchronized, see the documentation for more details. */ + @NonNull public Cursor rawQueryWithFactory( - CursorFactory cursorFactory, String sql, String[] selectionArgs, - String editTable) { + @Nullable CursorFactory cursorFactory, @NonNull String sql, + @Nullable String[] selectionArgs, @NonNull String editTable) { return rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable, null); } @@ -1762,7 +1794,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * @param sql the SQL query. The SQL string must not be ; terminated * @param selectionArgs You may include ?s in where clause in the query, * which will be replaced by the values from selectionArgs. The - * values will be bound as Strings. + * values will be bound as Strings. If selection is null or does not contain ?s then + * selectionArgs may be null. * @param editTable the name of the first table, which is editable * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown @@ -1770,9 +1803,11 @@ public final class SQLiteDatabase extends SQLiteClosable { * @return A {@link Cursor} object, which is positioned before the first entry. Note that * {@link Cursor}s are not synchronized, see the documentation for more details. */ + @NonNull public Cursor rawQueryWithFactory( - CursorFactory cursorFactory, String sql, String[] selectionArgs, - String editTable, CancellationSignal cancellationSignal) { + @Nullable CursorFactory cursorFactory, @NonNull String sql, + @Nullable String[] selectionArgs, @NonNull String editTable, + @Nullable CancellationSignal cancellationSignal) { acquireReference(); try { SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, @@ -1800,7 +1835,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * column values * @return the row ID of the newly inserted row, or -1 if an error occurred */ - public long insert(String table, String nullColumnHack, ContentValues values) { + public long insert(@NonNull String table, @Nullable String nullColumnHack, + @Nullable ContentValues values) { try { return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } catch (SQLException e) { @@ -1826,8 +1862,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws SQLException * @return the row ID of the newly inserted row, or -1 if an error occurred */ - public long insertOrThrow(String table, String nullColumnHack, ContentValues values) - throws SQLException { + public long insertOrThrow(@NonNull String table, @Nullable String nullColumnHack, + @Nullable ContentValues values) throws SQLException { return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } @@ -1847,7 +1883,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * the row. The keys should be the column names and the values the column values. * @return the row ID of the newly inserted row, or -1 if an error occurred */ - public long replace(String table, String nullColumnHack, ContentValues initialValues) { + public long replace(@NonNull String table, @Nullable String nullColumnHack, + @Nullable ContentValues initialValues) { try { return insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE); @@ -1874,8 +1911,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws SQLException * @return the row ID of the newly inserted row, or -1 if an error occurred */ - public long replaceOrThrow(String table, String nullColumnHack, - ContentValues initialValues) throws SQLException { + public long replaceOrThrow(@NonNull String table, @Nullable String nullColumnHack, + @Nullable ContentValues initialValues) throws SQLException { return insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE); } @@ -1899,8 +1936,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * input parameter <code>conflictAlgorithm</code> = {@link #CONFLICT_IGNORE} * or an error occurred. */ - public long insertWithOnConflict(String table, String nullColumnHack, - ContentValues initialValues, int conflictAlgorithm) { + public long insertWithOnConflict(@NonNull String table, @Nullable String nullColumnHack, + @Nullable ContentValues initialValues, int conflictAlgorithm) { acquireReference(); try { StringBuilder sql = new StringBuilder(); @@ -1950,12 +1987,14 @@ public final class SQLiteDatabase extends SQLiteClosable { * Passing null will delete all rows. * @param whereArgs You may include ?s in the where clause, which * will be replaced by the values from whereArgs. The values - * will be bound as Strings. + * will be bound as Strings. If whereClause is null or does not + * contain ?s then whereArgs may be null. * @return the number of rows affected if a whereClause is passed in, 0 * otherwise. To remove all rows and get a count pass "1" as the * whereClause. */ - public int delete(String table, String whereClause, String[] whereArgs) { + public int delete(@NonNull String table, @Nullable String whereClause, + @Nullable String[] whereArgs) { acquireReference(); try { SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + @@ -1980,10 +2019,12 @@ public final class SQLiteDatabase extends SQLiteClosable { * Passing null will update all rows. * @param whereArgs You may include ?s in the where clause, which * will be replaced by the values from whereArgs. The values - * will be bound as Strings. + * will be bound as Strings. If whereClause is null or does not + * contain ?s then whereArgs may be null. * @return the number of rows affected */ - public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { + public int update(@NonNull String table, @Nullable ContentValues values, + @Nullable String whereClause, @Nullable String[] whereArgs) { return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); } @@ -1997,12 +2038,13 @@ public final class SQLiteDatabase extends SQLiteClosable { * Passing null will update all rows. * @param whereArgs You may include ?s in the where clause, which * will be replaced by the values from whereArgs. The values - * will be bound as Strings. + * will be bound as Strings. If whereClause is null or does not + * contain ?s then whereArgs may be null. * @param conflictAlgorithm for update conflict resolver * @return the number of rows affected */ - public int updateWithOnConflict(String table, ContentValues values, - String whereClause, String[] whereArgs, int conflictAlgorithm) { + public int updateWithOnConflict(@NonNull String table, @Nullable ContentValues values, + @Nullable String whereClause, @Nullable String[] whereArgs, int conflictAlgorithm) { if (values == null || values.isEmpty()) { throw new IllegalArgumentException("Empty values"); } @@ -2125,7 +2167,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * @param bindArgs only byte[], String, Long and Double are supported in bindArgs. * @throws SQLException if the SQL string is invalid */ - public void execSQL(String sql, Object[] bindArgs) throws SQLException { + public void execSQL(@NonNull String sql, @NonNull Object[] bindArgs) + throws SQLException { if (bindArgs == null) { throw new IllegalArgumentException("Empty bindArgs"); } @@ -2133,7 +2176,8 @@ public final class SQLiteDatabase extends SQLiteClosable { } /** {@hide} */ - public int executeSql(String sql, Object[] bindArgs) throws SQLException { + public int executeSql(@NonNull String sql, @NonNull Object[] bindArgs) + throws SQLException { acquireReference(); try { final int statementType = DatabaseUtils.getSqlStatementType(sql); diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index ccc39b6080d7..039644387715 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -41,11 +41,6 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.RSIllegalArgumentException; -import android.renderscript.RenderScript; -import android.renderscript.Type; import android.text.TextUtils; import android.util.Log; import android.view.Surface; @@ -1007,132 +1002,6 @@ public class Camera { private native final void _addCallbackBuffer( byte[] callbackBuffer, int msgType); - /** - * <p>Create a {@link android.renderscript RenderScript} - * {@link android.renderscript.Allocation Allocation} to use as a - * destination of preview callback frames. Use - * {@link #setPreviewCallbackAllocation setPreviewCallbackAllocation} to use - * the created Allocation as a destination for camera preview frames.</p> - * - * <p>The Allocation will be created with a YUV type, and its contents must - * be accessed within Renderscript with the {@code rsGetElementAtYuv_*} - * accessor methods. Its size will be based on the current - * {@link Parameters#getPreviewSize preview size} configured for this - * camera.</p> - * - * @param rs the RenderScript context for this Allocation. - * @param usage additional usage flags to set for the Allocation. The usage - * flag {@link android.renderscript.Allocation#USAGE_IO_INPUT} will always - * be set on the created Allocation, but additional flags may be provided - * here. - * @return a new YUV-type Allocation with dimensions equal to the current - * preview size. - * @throws RSIllegalArgumentException if the usage flags are not compatible - * with an YUV Allocation. - * @see #setPreviewCallbackAllocation - * @hide - */ - public final Allocation createPreviewAllocation(RenderScript rs, int usage) - throws RSIllegalArgumentException { - Parameters p = getParameters(); - Size previewSize = p.getPreviewSize(); - Type.Builder yuvBuilder = new Type.Builder(rs, - Element.createPixel(rs, - Element.DataType.UNSIGNED_8, - Element.DataKind.PIXEL_YUV)); - // Use YV12 for wide compatibility. Changing this requires also - // adjusting camera service's format selection. - yuvBuilder.setYuvFormat(ImageFormat.YV12); - yuvBuilder.setX(previewSize.width); - yuvBuilder.setY(previewSize.height); - - Allocation a = Allocation.createTyped(rs, yuvBuilder.create(), - usage | Allocation.USAGE_IO_INPUT); - - return a; - } - - /** - * <p>Set an {@link android.renderscript.Allocation Allocation} as the - * target of preview callback data. Use this method for efficient processing - * of camera preview data with RenderScript. The Allocation must be created - * with the {@link #createPreviewAllocation createPreviewAllocation } - * method.</p> - * - * <p>Setting a preview allocation will disable any active preview callbacks - * set by {@link #setPreviewCallback setPreviewCallback} or - * {@link #setPreviewCallbackWithBuffer setPreviewCallbackWithBuffer}, and - * vice versa. Using a preview allocation still requires an active standard - * preview target to be set, either with - * {@link #setPreviewTexture setPreviewTexture} or - * {@link #setPreviewDisplay setPreviewDisplay}.</p> - * - * <p>To be notified when new frames are available to the Allocation, use - * {@link android.renderscript.Allocation#setIoInputNotificationHandler Allocation.setIoInputNotificationHandler}. To - * update the frame currently accessible from the Allocation to the latest - * preview frame, call - * {@link android.renderscript.Allocation#ioReceive Allocation.ioReceive}.</p> - * - * <p>To disable preview into the Allocation, call this method with a - * {@code null} parameter.</p> - * - * <p>Once a preview allocation is set, the preview size set by - * {@link Parameters#setPreviewSize setPreviewSize} cannot be changed. If - * you wish to change the preview size, first remove the preview allocation - * by calling {@code setPreviewCallbackAllocation(null)}, then change the - * preview size, create a new preview Allocation with - * {@link #createPreviewAllocation createPreviewAllocation}, and set it as - * the new preview callback allocation target.</p> - * - * <p>If you are using the preview data to create video or still images, - * strongly consider using {@link android.media.MediaActionSound} to - * properly indicate image capture or recording start/stop to the user.</p> - * - * @param previewAllocation the allocation to use as destination for preview - * @throws IOException if configuring the camera to use the Allocation for - * preview fails. - * @throws IllegalArgumentException if the Allocation's dimensions or other - * parameters don't meet the requirements. - * @see #createPreviewAllocation - * @see #setPreviewCallback - * @see #setPreviewCallbackWithBuffer - * @hide - */ - public final void setPreviewCallbackAllocation(Allocation previewAllocation) - throws IOException { - Surface previewSurface = null; - if (previewAllocation != null) { - Parameters p = getParameters(); - Size previewSize = p.getPreviewSize(); - if (previewSize.width != previewAllocation.getType().getX() || - previewSize.height != previewAllocation.getType().getY()) { - throw new IllegalArgumentException( - "Allocation dimensions don't match preview dimensions: " + - "Allocation is " + - previewAllocation.getType().getX() + - ", " + - previewAllocation.getType().getY() + - ". Preview is " + previewSize.width + ", " + - previewSize.height); - } - if ((previewAllocation.getUsage() & - Allocation.USAGE_IO_INPUT) == 0) { - throw new IllegalArgumentException( - "Allocation usage does not include USAGE_IO_INPUT"); - } - if (previewAllocation.getType().getElement().getDataKind() != - Element.DataKind.PIXEL_YUV) { - throw new IllegalArgumentException( - "Allocation is not of a YUV type"); - } - previewSurface = previewAllocation.getSurface(); - mUsingPreviewAllocation = true; - } else { - mUsingPreviewAllocation = false; - } - setPreviewCallbackSurface(previewSurface); - } - private native final void setPreviewCallbackSurface(Surface s); private class EventHandler extends Handler diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 0e45787c1340..082a3361be4e 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -541,14 +541,6 @@ public abstract class CameraDevice implements AutoCloseable { * or configuring it to use one of the supported * {@link android.media.CamcorderProfile CamcorderProfiles}.</li> * - * <li>For efficient YUV processing with {@link android.renderscript}: - * Create a RenderScript - * {@link android.renderscript.Allocation Allocation} with a supported YUV - * type, the IO_INPUT flag, and one of the sizes returned by - * {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(Allocation.class)}, - * Then obtain the Surface with - * {@link android.renderscript.Allocation#getSurface}.</li> - * * <li>For access to RAW, uncompressed YUV, or compressed JPEG data in the application: Create an * {@link android.media.ImageReader} object with one of the supported output formats given by * {@link StreamConfigurationMap#getOutputFormats()}, setting its size to one of the diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index ef0db7f8a41c..b85d6869a1ff 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -509,8 +509,6 @@ public final class StreamConfigurationMap { * Recommended for recording video (simple to use) * <li>{@link android.media.MediaCodec} - * Recommended for recording video (more complicated to use, with more flexibility) - * <li>{@link android.renderscript.Allocation} - - * Recommended for image processing with {@link android.renderscript RenderScript} * <li>{@link android.view.SurfaceHolder} - * Recommended for low-power camera preview with {@link android.view.SurfaceView} * <li>{@link android.graphics.SurfaceTexture} - diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index af09a0662795..5b24dcacbf53 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -1294,32 +1294,30 @@ public final class FileUtils { * Round the given size of a storage device to a nice round power-of-two * value, such as 256MB or 32GB. This avoids showing weird values like * "29.5GB" in UI. - * - * Some storage devices are still using GiB (powers of 1024) over - * GB (powers of 1000) measurements and this method takes it into account. - * * Round ranges: * ... - * [256 GiB + 1; 512 GiB] -> 512 GB - * [512 GiB + 1; 1 TiB] -> 1 TB - * [1 TiB + 1; 2 TiB] -> 2 TB + * (128 GB; 256 GB] -> 256 GB + * (256 GB; 512 GB] -> 512 GB + * (512 GB; 1000 GB] -> 1000 GB + * (1000 GB; 2000 GB] -> 2000 GB + * ... * etc * * @hide */ public static long roundStorageSize(long size) { long val = 1; - long kiloPow = 1; - long kibiPow = 1; - while ((val * kibiPow) < size) { + long pow = 1; + while ((val * pow) < size) { val <<= 1; if (val > 512) { val = 1; - kibiPow *= 1024; - kiloPow *= 1000; + pow *= 1000; } } - return val * kiloPow; + + Log.d(TAG, String.format("Rounded bytes from %d to %d", size, val * pow)); + return val * pow; } private static long toBytes(long value, String unit) { diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index bc52744078ea..369a1932e437 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -174,4 +174,5 @@ interface IStorageManager { boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 95; void setCloudMediaProvider(in String authority) = 96; String getCloudMediaProvider() = 97; + long getInternalStorageBlockDeviceSize() = 98; }
\ No newline at end of file diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 80dd48825ba7..ee387e7c284f 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1359,6 +1359,15 @@ public class StorageManager { } /** {@hide} */ + public long getInternalStorageBlockDeviceSize() { + try { + return mStorageManager.getInternalStorageBlockDeviceSize(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ public void mkdirs(File file) { BlockGuard.getVmPolicy().onPathAccess(file.getAbsolutePath()); try { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2791c412f5c7..b9a658344e7d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4579,6 +4579,16 @@ public final class Settings { public static final String WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE = "wear_accessibility_gesture_enabled_during_oobe"; + + /** + * If the text-to-speech pre-warm is enabled. + * Set to 1 for true and 0 for false. + * + * This setting is used only internally. + * @hide + */ + public static final String WEAR_TTS_PREWARM_ENABLED = "wear_tts_prewarm_enabled"; + /** * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_ON} instead */ @@ -6017,6 +6027,7 @@ public final class Settings { PRIVATE_SETTINGS.add(ADVANCED_SETTINGS); PRIVATE_SETTINGS.add(WEAR_ACCESSIBILITY_GESTURE_ENABLED); PRIVATE_SETTINGS.add(WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE); + PRIVATE_SETTINGS.add(WEAR_TTS_PREWARM_ENABLED); PRIVATE_SETTINGS.add(SCREEN_AUTO_BRIGHTNESS_ADJ); PRIVATE_SETTINGS.add(VIBRATE_INPUT_DEVICES); PRIVATE_SETTINGS.add(VOLUME_MASTER); diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS index 4e8d6e70091c..22b1f026e2ae 100644 --- a/core/java/android/security/OWNERS +++ b/core/java/android/security/OWNERS @@ -1,9 +1,9 @@ # Bug component: 36824 -cbrubaker@google.com +brambonne@google.com +brufino@google.com +jeffv@google.com -per-file NetworkSecurityPolicy.java = cbrubaker@google.com -per-file NetworkSecurityPolicy.java = klyubin@google.com -per-file FrameworkNetworkSecurityPolicy.java = cbrubaker@google.com -per-file FrameworkNetworkSecurityPolicy.java = klyubin@google.com +per-file *NetworkSecurityPolicy.java = file:net/OWNERS per-file Confirmation*.java = file:/keystore/OWNERS +per-file FileIntegrityManager.java = victorhsieh@google.com diff --git a/core/java/android/security/net/OWNERS b/core/java/android/security/net/OWNERS index d8281645738b..1d52eed0cb56 100644 --- a/core/java/android/security/net/OWNERS +++ b/core/java/android/security/net/OWNERS @@ -1,4 +1,4 @@ # Bug component: 36824 -cbrubaker@google.com brambonne@google.com +jeffv@google.com diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index 511cb2df712d..ac76fc2eb469 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -212,6 +212,11 @@ public class IntArray implements Cloneable { return -1; } + /** Returns {@code true} if this array contains the specified value. */ + public boolean contains(int value) { + return indexOf(value) != -1; + } + /** * Removes the value at the specified index from this array. */ diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 6e73a3c93fd7..23afb03569ce 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -346,13 +346,13 @@ public class HandwritingInitiator { } private static boolean shouldTriggerStylusHandwritingForView(@NonNull View view) { - if (!view.isAutoHandwritingEnabled()) { + if (!view.shouldInitiateHandwriting()) { return false; } - // The view may be a handwriting initiation delegate, in which case it is not the editor + // The view may be a handwriting initiation delegator, in which case it is not the editor // view for which handwriting would be started. However, in almost all cases, the return - // values of View#isStylusHandwritingAvailable will be the same for the delegate view and - // the delegator editor view. So the delegate view can be used to decide whether handwriting + // values of View#isStylusHandwritingAvailable will be the same for the delegator view and + // the delegate editor view. So the delegator view can be used to decide whether handwriting // should be triggered. return view.isStylusHandwritingAvailable(); } @@ -677,7 +677,7 @@ public class HandwritingInitiator { /** The helper method to check if the given view is still active for handwriting. */ private static boolean isViewActive(@Nullable View view) { return view != null && view.isAttachedToWindow() && view.isAggregatedVisible() - && view.isAutoHandwritingEnabled(); + && view.shouldInitiateHandwriting(); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 5e19c675bb88..87036099239d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5488,7 +5488,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) | (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) | (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT); - mPrivateFlags4 = PFLAG4_AUTO_HANDWRITING_ENABLED; final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); @@ -6213,7 +6212,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPreferKeepClear(a.getBoolean(attr, false)); break; case R.styleable.View_autoHandwritingEnabled: - setAutoHandwritingEnabled(a.getBoolean(attr, true)); + setAutoHandwritingEnabled(a.getBoolean(attr, false)); break; case R.styleable.View_handwritingBoundsOffsetLeft: mHandwritingBoundsOffsetLeft = a.getDimension(attr, 0); @@ -12150,7 +12149,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (getSystemGestureExclusionRects().isEmpty() && collectPreferKeepClearRects().isEmpty() && collectUnrestrictedPreferKeepClearRects().isEmpty() - && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) { + && (info.mHandwritingArea == null || !shouldInitiateHandwriting())) { if (info.mPositionUpdateListener != null) { mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener); info.mPositionUpdateListener = null; @@ -12517,7 +12516,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void updateHandwritingArea() { // If autoHandwritingArea is not enabled, do nothing. - if (!isAutoHandwritingEnabled()) return; + if (!shouldInitiateHandwriting()) return; final AttachInfo ai = mAttachInfo; if (ai != null) { ai.mViewRootImpl.getHandwritingInitiator().updateHandwritingAreasForView(this); @@ -12525,6 +12524,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns true if a stylus {@link MotionEvent} within this view's bounds should initiate + * handwriting mode, either for this view ({@link #isAutoHandwritingEnabled()} is {@code true}) + * or for a handwriting delegate view ({@link #getHandwritingDelegatorCallback()} is not {@code + * null}). + */ + boolean shouldInitiateHandwriting() { + return isAutoHandwritingEnabled() || getHandwritingDelegatorCallback() != null; + } + + /** * Sets a callback which should be called when a stylus {@link MotionEvent} occurs within this * view's bounds. The callback will be called from the UI thread. * diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index e1de05bda9df..79acfbb0b39c 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -801,6 +801,11 @@ public class RemoteViews implements Parcelable, Filter { mActions.set(i, new SetRemoteCollectionItemListAdapterAction(itemsAction.viewId, itemsAction.mServiceIntent)); isActionReplaced = true; + } else if (action instanceof SetRemoteViewsAdapterIntent intentAction + && intentAction.viewId == viewId) { + mActions.set(i, new SetRemoteCollectionItemListAdapterAction( + intentAction.viewId, intentAction.intent)); + isActionReplaced = true; } else if (action instanceof ViewGroupActionAdd groupAction && groupAction.mNestedViews != null) { isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId); @@ -822,6 +827,42 @@ public class RemoteViews implements Parcelable, Filter { return isActionReplaced; } + /** + * @return True if has set remote adapter using service intent + * @hide + */ + public boolean hasLegacyLists() { + if (mActions != null) { + for (int i = 0; i < mActions.size(); i++) { + Action action = mActions.get(i); + if ((action instanceof SetRemoteCollectionItemListAdapterAction itemsAction + && itemsAction.mServiceIntent != null) + || (action instanceof SetRemoteViewsAdapterIntent intentAction + && intentAction.intent != null) + || (action instanceof ViewGroupActionAdd groupAction + && groupAction.mNestedViews != null + && groupAction.mNestedViews.hasLegacyLists())) { + return true; + } + } + } + if (mSizedRemoteViews != null) { + for (int i = 0; i < mSizedRemoteViews.size(); i++) { + if (mSizedRemoteViews.get(i).hasLegacyLists()) { + return true; + } + } + } + if (mLandscape != null && mLandscape.hasLegacyLists()) { + return true; + } + if (mPortrait != null && mPortrait.hasLegacyLists()) { + return true; + } + + return false; + } + private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { if (icon != null && (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8899f5cd91d4..0001fe6164a3 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1856,6 +1856,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean clickable = canInputOrMove || isClickable(); boolean longClickable = canInputOrMove || isLongClickable(); int focusable = getFocusable(); + boolean isAutoHandwritingEnabled = true; n = a.getIndexCount(); for (int i = 0; i < n; i++) { @@ -1878,6 +1879,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.View_longClickable: longClickable = a.getBoolean(attr, longClickable); break; + + case com.android.internal.R.styleable.View_autoHandwritingEnabled: + isAutoHandwritingEnabled = a.getBoolean(attr, true); + break; } } a.recycle(); @@ -1891,6 +1896,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } setClickable(clickable); setLongClickable(longClickable); + setAutoHandwritingEnabled(isAutoHandwritingEnabled); if (mEditor != null) mEditor.prepareCursorControllers(); diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig new file mode 100644 index 000000000000..560e41b1aa33 --- /dev/null +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -0,0 +1,10 @@ +package: "com.android.window.flags" + +# Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes + +flag { + namespace: "windowing_sdk" + name: "sync_window_config_update_flag" + description: "Whether the feature to sync different window-related config updates is enabled" + bug: "260873529" +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f55501ab1a8f..ffd640fe36ea 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2267,6 +2267,14 @@ <permission android:name="android.permission.MANAGE_ETHERNET_NETWORKS" android:protectionLevel="signature" /> + <!-- Allows system apps to call methods to register itself as a mDNS offload engine. + <p>Not for use by third-party or privileged applications. + @SystemApi + @hide This should only be used by system apps. + --> + <permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE" + android:protectionLevel="signature" /> + <!-- ======================================= --> <!-- Permissions for short range, peripheral networks --> <!-- ======================================= --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index aeeaacaacf11..281053b6ce7b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1210,13 +1210,16 @@ <integer name="config_triplePressOnStemPrimaryBehavior">0</integer> <!-- Control the behavior when the user short presses the stem primary button. - Stem primary button is only used on watch form factor. If a device is not - a watch, setting this config is no-op. - 0 - Nothing - 1 - Go to launch all apps + Stem primary button is only used on watch form factor. If a device is not + a watch, setting this config is no-op. + 0 - Nothing + 1 - Go to launch all apps + 2 - Launch target activity defined by config_primaryShortPressTargetActivity if available --> <integer name="config_shortPressOnStemPrimaryBehavior">0</integer> + <!-- Activity name for the default target activity to be launched. [DO NOT TRANSLATE] --> + <string name="config_primaryShortPressTargetActivity" translatable="false"></string> <!-- Control the behavior of the search key. 0 - Launch default search activity @@ -5356,6 +5359,7 @@ <item>1,1,1.0,0,1</item> <item>1,1,1.0,.4,1</item> <item>1,1,1.0,.15,15</item> + <item>0,0,0.7,0,1</item> </string-array> <!-- The integer index of the selected option in config_udfps_touch_detection_options --> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index bda194add759..6bb87f3c4e99 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -73,7 +73,7 @@ CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY. If 0, the device always switch to the higher score SIM. If < 0, the network type and signal strength based auto switch is disabled. --> - <integer name="auto_data_switch_score_tolerance">3000</integer> + <integer name="auto_data_switch_score_tolerance">-1</integer> <java-symbol type="integer" name="auto_data_switch_score_tolerance" /> <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8330f7bdc251..aa0cbfa92043 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -466,6 +466,7 @@ <java-symbol type="integer" name="config_shortPressOnSleepBehavior" /> <java-symbol type="integer" name="config_longPressOnStemPrimaryBehavior" /> <java-symbol type="integer" name="config_shortPressOnStemPrimaryBehavior" /> + <java-symbol type="string" name="config_primaryShortPressTargetActivity" /> <java-symbol type="integer" name="config_doublePressOnStemPrimaryBehavior" /> <java-symbol type="integer" name="config_triplePressOnStemPrimaryBehavior" /> <java-symbol type="string" name="config_doublePressOnPowerTargetActivity" /> diff --git a/core/tests/coretests/res/values-id/strings.xml b/core/tests/coretests/res/values-id/strings.xml new file mode 100644 index 000000000000..6d71c90866cc --- /dev/null +++ b/core/tests/coretests/res/values-id/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Used in ResourcesLocaleTest. --> + <string name="locale_test_res_1">Pengujian ID</string> +</resources> diff --git a/core/tests/coretests/res/values-in/strings.xml b/core/tests/coretests/res/values-in/strings.xml new file mode 100644 index 000000000000..63846603ed60 --- /dev/null +++ b/core/tests/coretests/res/values-in/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Used in ResourcesLocaleTest. --> + <string name="locale_test_res_2">Pengujian IN</string> +</resources> diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml index e51eab60a998..09e1c690f4e2 100644 --- a/core/tests/coretests/res/values/strings.xml +++ b/core/tests/coretests/res/values/strings.xml @@ -131,6 +131,13 @@ <string name="textview_hebrew_text">םמab?!</string> + <!-- Used in ResourcesLocaleTest. Also defined in values-id. "id" is the new ISO code for Indonesian. --> + <string name="locale_test_res_1">Testing ID</string> + <!-- Used in ResourcesLocaleTest. Also defined in values-in. "in" is the deprecated ISO code for Indonesian. --> + <string name="locale_test_res_2">Testing IN</string> + <!-- Used in ResourcesLocaleTest. --> + <string name="locale_test_res_3">Testing EN</string> + <!-- SizeAdaptiveLayout --> <string name="first">Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.</string> <string name="actor">Abe Lincoln</string> diff --git a/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java b/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java index 25c3db5c6910..26e4349243a5 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java @@ -16,6 +16,7 @@ package android.content.res; +import android.content.Context; import android.os.FileUtils; import android.os.LocaleList; import android.platform.test.annotations.Presubmit; @@ -97,4 +98,24 @@ public class ResourcesLocaleTest extends AndroidTestCase { assertEquals(Locale.forLanguageTag("pl-PL"), resources.getConfiguration().getLocales().get(0)); } + + @SmallTest + public void testDeprecatedISOLanguageCode() { + assertResGetString(Locale.US, R.string.locale_test_res_1, "Testing ID"); + assertResGetString(Locale.forLanguageTag("id"), R.string.locale_test_res_2, "Pengujian IN"); + assertResGetString(Locale.forLanguageTag("id"), R.string.locale_test_res_3, "Testing EN"); + assertResGetString(new Locale("id"), R.string.locale_test_res_2, "Pengujian IN"); + assertResGetString(new Locale("id"), R.string.locale_test_res_3, "Testing EN"); + // The new ISO code "id" isn't supported yet, and thus the values-id are ignored. + assertResGetString(new Locale("id"), R.string.locale_test_res_1, "Testing ID"); + assertResGetString(Locale.forLanguageTag("id"), R.string.locale_test_res_1, "Testing ID"); + } + + private void assertResGetString(Locale locale, int resId, String expectedString) { + LocaleList locales = new LocaleList(locale); + final Configuration config = new Configuration(); + config.setLocales(locales); + Context newContext = getContext().createConfigurationContext(config); + assertEquals(expectedString, newContext.getResources().getString(resId)); + } } diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index 394ff0ae9a2e..a0d8183b8da7 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -505,45 +505,32 @@ public class FileUtilsTest { @Test public void testRoundStorageSize() throws Exception { - final long GB1 = DataUnit.GIGABYTES.toBytes(1); - final long GiB1 = DataUnit.GIBIBYTES.toBytes(1); - final long GB2 = DataUnit.GIGABYTES.toBytes(2); - final long GiB2 = DataUnit.GIBIBYTES.toBytes(2); - final long GiB128 = DataUnit.GIBIBYTES.toBytes(128); - final long GB256 = DataUnit.GIGABYTES.toBytes(256); - final long GiB256 = DataUnit.GIBIBYTES.toBytes(256); - final long GB512 = DataUnit.GIGABYTES.toBytes(512); - final long GiB512 = DataUnit.GIBIBYTES.toBytes(512); - final long TB1 = DataUnit.TERABYTES.toBytes(1); - final long TiB1 = DataUnit.TEBIBYTES.toBytes(1); - final long TB2 = DataUnit.TERABYTES.toBytes(2); - final long TiB2 = DataUnit.TEBIBYTES.toBytes(2); - final long TB4 = DataUnit.TERABYTES.toBytes(4); - final long TiB4 = DataUnit.TEBIBYTES.toBytes(4); - final long TB8 = DataUnit.TERABYTES.toBytes(8); - final long TiB8 = DataUnit.TEBIBYTES.toBytes(8); - - assertEquals(GB1, roundStorageSize(GB1 - 1)); - assertEquals(GB1, roundStorageSize(GB1)); - assertEquals(GB1, roundStorageSize(GB1 + 1)); - assertEquals(GB1, roundStorageSize(GiB1 - 1)); - assertEquals(GB1, roundStorageSize(GiB1)); - assertEquals(GB2, roundStorageSize(GiB1 + 1)); - assertEquals(GB2, roundStorageSize(GiB2)); - - assertEquals(GB256, roundStorageSize(GiB128 + 1)); - assertEquals(GB256, roundStorageSize(GiB256)); - assertEquals(GB512, roundStorageSize(GiB256 + 1)); - assertEquals(GB512, roundStorageSize(GiB512)); - assertEquals(TB1, roundStorageSize(GiB512 + 1)); - assertEquals(TB1, roundStorageSize(TiB1)); - assertEquals(TB2, roundStorageSize(TiB1 + 1)); - assertEquals(TB2, roundStorageSize(TiB2)); - assertEquals(TB4, roundStorageSize(TiB2 + 1)); - assertEquals(TB4, roundStorageSize(TiB4)); - assertEquals(TB8, roundStorageSize(TiB4 + 1)); - assertEquals(TB8, roundStorageSize(TiB8)); - assertEquals(TB1, roundStorageSize(1013077688320L)); // b/268571529 + final long M256 = DataUnit.MEGABYTES.toBytes(256); + final long M512 = DataUnit.MEGABYTES.toBytes(512); + final long G1 = DataUnit.GIGABYTES.toBytes(1); + final long G2 = DataUnit.GIGABYTES.toBytes(2); + final long G32 = DataUnit.GIGABYTES.toBytes(32); + final long G64 = DataUnit.GIGABYTES.toBytes(64); + final long G512 = DataUnit.GIGABYTES.toBytes(512); + final long G1000 = DataUnit.TERABYTES.toBytes(1); + final long G2000 = DataUnit.TERABYTES.toBytes(2); + + assertEquals(M256, roundStorageSize(M256 - 1)); + assertEquals(M256, roundStorageSize(M256)); + assertEquals(M512, roundStorageSize(M256 + 1)); + assertEquals(M512, roundStorageSize(M512 - 1)); + assertEquals(M512, roundStorageSize(M512)); + assertEquals(G1, roundStorageSize(M512 + 1)); + assertEquals(G1, roundStorageSize(G1)); + assertEquals(G2, roundStorageSize(G1 + 1)); + + assertEquals(G32, roundStorageSize(G32 - 1)); + assertEquals(G32, roundStorageSize(G32)); + assertEquals(G64, roundStorageSize(G32 + 1)); + + assertEquals(G512, roundStorageSize(G512 - 1)); + assertEquals(G1000, roundStorageSize(G512 + 1)); + assertEquals(G2000, roundStorageSize(G1000 + 1)); } @Test diff --git a/core/tests/coretests/src/android/service/TEST_MAPPING b/core/tests/coretests/src/android/service/TEST_MAPPING new file mode 100644 index 000000000000..fbf8a92e031d --- /dev/null +++ b/core/tests/coretests/src/android/service/TEST_MAPPING @@ -0,0 +1,18 @@ +{ + "postsubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + {"include-filter": "android.service.controls"}, + {"include-filter": "android.service.controls.actions"}, + {"include-filter": "android.service.controls.templates"}, + {"include-filter": "android.service.euicc"}, + {"include-filter": "android.service.notification"}, + {"include-filter": "android.service.quicksettings"}, + {"include-filter": "android.service.settings.suggestions"}, + {"include-filter": "android.service.timezone"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ] +} diff --git a/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java b/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java index 0af8c728aba3..6792d0b91084 100644 --- a/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java +++ b/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java @@ -239,7 +239,7 @@ public class EuiccProfileInfoTest { assertNotEquals(p.hashCode(), t.hashCode()); } - @Test(expected = IllegalStateException.class) + @Test(expected = IllegalArgumentException.class) public void testBuilderBuild_IllegalIccid() { new EuiccProfileInfo.Builder("abc").build(); } diff --git a/core/tests/coretests/src/android/service/euicc/OWNERS b/core/tests/coretests/src/android/service/euicc/OWNERS new file mode 100644 index 000000000000..41fc56b45dde --- /dev/null +++ b/core/tests/coretests/src/android/service/euicc/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/telephony/java/android/service/euicc/OWNERS diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index f1eef7551dd5..c46118db617f 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -245,7 +245,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() { - View delegateView = new View(mContext); + View delegateView = new EditText(mContext); delegateView.setIsHandwritingDelegate(true); mTestView1.setHandwritingDelegatorCallback( @@ -266,7 +266,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() { - View delegateView = new View(mContext); + View delegateView = new EditText(mContext); delegateView.setIsHandwritingDelegate(true); mHandwritingInitiator.onInputConnectionCreated(delegateView); reset(mHandwritingInitiator); diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java new file mode 100644 index 000000000000..a8b40325a713 --- /dev/null +++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java @@ -0,0 +1,45 @@ +/* + * 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.window.flags; + +import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link com.android.window.flags.Flags} + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowFlagsTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowFlagsTest { + + @Test + public void testSyncWindowConfigUpdateFlag() { + // No crash when accessing the flag. + syncWindowConfigUpdateFlag(); + } +} diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java index caa7312a4475..ad5c4ee83e54 100644 --- a/core/tests/utiltests/src/android/util/IntArrayTest.java +++ b/core/tests/utiltests/src/android/util/IntArrayTest.java @@ -55,9 +55,11 @@ public class IntArrayTest { a.add(5, 20); assertThat(a.get(5)).isEqualTo(20); assertThat(a.indexOf(20)).isEqualTo(5); + assertThat(a.contains(20)).isTrue(); verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0); assertThat(a.indexOf(99)).isEqualTo(-1); + assertThat(a.contains(99)).isFalse(); a.resize(15); a.set(14, 30); @@ -71,6 +73,7 @@ public class IntArrayTest { backingArray[2] = 30; verify(a, backingArray); assertThat(a.indexOf(30)).isEqualTo(2); + assertThat(a.contains(30)).isTrue(); a.resize(2); assertThat(backingArray[2]).isEqualTo(0); 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 81384ca35d52..a5ee19e2d068 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -261,10 +261,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Updates the Split final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); final WindowContainerTransaction wct = transactionRecord.getTransaction(); - - mPresenter.setTaskFragmentIsolatedNavigation(wct, - splitPinContainer.getSecondaryContainer().getTaskFragmentToken(), - true /* isolatedNav */); mPresenter.updateSplitContainer(splitPinContainer, wct); transactionRecord.apply(false /* shouldApplyIndependently */); updateCallbackIfNecessary(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 5de6acfcc9db..896fe61b5611 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -382,6 +382,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(), secondaryContainer.getTaskFragmentToken(), splitRule, isStacked); + + // Setting isolated navigation and clear non-sticky pinned container if needed. + final SplitPinRule splitPinRule = + splitRule instanceof SplitPinRule ? (SplitPinRule) splitRule : null; + if (splitPinRule == null) { + return; + } + + setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(), + !isStacked /* isolatedNav */); + if (isStacked && !splitPinRule.isSticky()) { + secondaryContainer.getTaskContainer().removeSplitPinContainer(); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index ff67110634ba..9a2b81243861 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -851,7 +851,10 @@ public class Bubble implements BubbleViewProvider { return mAppIntent; } - boolean isAppBubble() { + /** + * Returns whether this bubble is from an app versus a notification. + */ + public boolean isAppBubble() { return mIsAppBubble; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java index 250e010f4d69..76662c47238f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java @@ -52,6 +52,11 @@ public class BubbleDebugConfig { private static final boolean FORCE_SHOW_USER_EDUCATION = false; private static final String FORCE_SHOW_USER_EDUCATION_SETTING = "force_show_bubbles_user_education"; + /** + * When set to true, bubbles user education flow never shows up. + */ + private static final String FORCE_HIDE_USER_EDUCATION_SETTING = + "force_hide_bubbles_user_education"; /** * @return whether we should force show user education for bubbles. Used for debugging & demos. @@ -62,6 +67,14 @@ public class BubbleDebugConfig { return FORCE_SHOW_USER_EDUCATION || forceShow; } + /** + * @return whether we should never show user education for bubbles. Used in tests. + */ + static boolean neverShowUserEducation(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + FORCE_HIDE_USER_EDUCATION_SETTING, 0) != 0; + } + static String formatBubblesString(List<Bubble> bubbles, BubbleViewProvider selected) { StringBuilder sb = new StringBuilder(); for (Bubble bubble : bubbles) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 2c100653dae0..ea7053d8ee49 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -653,14 +653,38 @@ public class BubblePositioner { } /** - * @return the stack position to use if we don't have a saved location or if user education - * is being shown. + * Returns whether the {@link #getRestingPosition()} is equal to the default start position + * initialized for bubbles, if {@code true} this means the user hasn't moved the bubble + * from the initial start position (or they haven't received a bubble yet). + */ + public boolean hasUserModifiedDefaultPosition() { + PointF defaultStart = getDefaultStartPosition(); + return mRestingStackPosition != null + && !mRestingStackPosition.equals(defaultStart); + } + + /** + * Returns the stack position to use if we don't have a saved location or if user education + * is being shown, for a normal bubble. */ public PointF getDefaultStartPosition() { - // Start on the left if we're in LTR, right otherwise. - final boolean startOnLeft = - mContext.getResources().getConfiguration().getLayoutDirection() - != LAYOUT_DIRECTION_RTL; + return getDefaultStartPosition(false /* isAppBubble */); + } + + /** + * The stack position to use if we don't have a saved location or if user education + * is being shown. + * + * @param isAppBubble whether this start position is for an app bubble or not. + */ + public PointF getDefaultStartPosition(boolean isAppBubble) { + final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection(); + // Normal bubbles start on the left if we're in LTR, right otherwise. + // TODO (b/294284894): update language around "app bubble" here + // App bubbles start on the right in RTL, left otherwise. + final boolean startOnLeft = isAppBubble + ? layoutDirection == LAYOUT_DIRECTION_RTL + : layoutDirection != LAYOUT_DIRECTION_RTL; final RectF allowableStackPositionRegion = getAllowableStackPositionRegion( 1 /* default starts with 1 bubble */); if (isLargeScreen()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 8ae12c75b3ee..52c9bf8462ec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -1284,6 +1284,12 @@ public class BubbleStackView extends FrameLayout if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show manage edu: " + shouldShow); } + if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { + if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { + Log.d(TAG, "Want to show manage edu, but it is forced hidden"); + } + return false; + } return shouldShow; } @@ -1316,6 +1322,12 @@ public class BubbleStackView extends FrameLayout if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { Log.d(TAG, "Show stack edu: " + shouldShow); } + if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) { + if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { + Log.d(TAG, "Want to show stack edu, but it is forced hidden"); + } + return false; + } return shouldShow; } @@ -1763,13 +1775,26 @@ public class BubbleStackView extends FrameLayout return; } + if (firstBubble && bubble.isAppBubble() && !mPositioner.hasUserModifiedDefaultPosition()) { + // TODO (b/294284894): update language around "app bubble" here + // If it's an app bubble and we don't have a previous resting position, update the + // controllers to use the default position for the app bubble (it'd be different from + // the position initialized with the controllers originally). + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition); + mStackAnimationController.setStackPosition(startPosition); + mExpandedAnimationController.setCollapsePoint(startPosition); + // Set the translation x so that this bubble will animate in from the same side they + // expand / collapse on. + bubble.getIconView().setTranslationX(startPosition.x); + } else if (firstBubble) { + mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); + } + mBubbleContainer.addView(bubble.getIconView(), 0, new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); - if (firstBubble) { - mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); - } // Set the dot position to the opposite of the side the stack is resting on, since the stack // resting slightly off-screen would result in the dot also being off-screen. bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index c20733af2ba5..4d7042bbb3d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -131,6 +131,16 @@ public class ExpandedAnimationController private BubbleStackView mBubbleStackView; + /** + * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause + * the rest of the bubbles to animate to fill the gap. + */ + private boolean mBubbleDraggedOutEnough = false; + + /** End action to run when the lead bubble's expansion animation completes. */ + @Nullable + private Runnable mLeadBubbleEndAction; + public ExpandedAnimationController(BubblePositioner positioner, Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) { mPositioner = positioner; @@ -141,14 +151,12 @@ public class ExpandedAnimationController } /** - * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause - * the rest of the bubbles to animate to fill the gap. + * Overrides the collapse location without actually collapsing the stack. + * @param point the new collapse location. */ - private boolean mBubbleDraggedOutEnough = false; - - /** End action to run when the lead bubble's expansion animation completes. */ - @Nullable - private Runnable mLeadBubbleEndAction; + public void setCollapsePoint(PointF point) { + mCollapsePoint = point; + } /** * Animates expanding the bubbles into a row along the top of the screen, optionally running an diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 4bb1ab4d0f54..aad268394305 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -297,9 +297,6 @@ public class StackAnimationController extends /** Whether the stack is on the left side of the screen. */ public boolean isStackOnLeftSide() { - if (mLayout == null || !isStackPositionSet()) { - return true; // Default to left, which is where it starts by default. - } return mPositioner.isStackOnLeft(mStackPosition); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt index 8b5283d83683..1fd22d0a3505 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt @@ -85,7 +85,7 @@ class BubblePopupDrawable(private val config: Config) : Drawable() { canvas.drawPath(path, paint) } - override fun onBoundsChange(bounds: Rect?) { + override fun onBoundsChange(bounds: Rect) { requestPathUpdate() } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt index a141ff951684..4abb35c2a428 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt @@ -19,7 +19,6 @@ import android.app.AppOpsManager import android.content.Context import android.content.pm.PackageManager import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.pip.PipUtils class PipAppOpsListener( private val mContext: Context, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt index 2719cd29009e..427a555eee92 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt @@ -33,7 +33,6 @@ import android.os.Handler import android.os.HandlerExecutor import android.os.UserHandle import com.android.wm.shell.R -import com.android.wm.shell.pip.PipUtils import java.util.function.Consumer /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt new file mode 100644 index 000000000000..84feb03e6a40 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.common.pip + +import android.app.ActivityTaskManager +import android.app.RemoteAction +import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.Context +import android.os.RemoteException +import android.os.SystemProperties +import android.util.DisplayMetrics +import android.util.Log +import android.util.Pair +import android.util.TypedValue +import android.window.TaskSnapshot +import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.protolog.ShellProtoLogGroup +import kotlin.math.abs + +/** A class that includes convenience methods. */ +object PipUtils { + private const val TAG = "PipUtils" + + // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. + private const val EPSILON = 1e-7 + private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation" + + /** + * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. + * The component name may be null if no such activity exists. + */ + @JvmStatic + fun getTopPipActivity(context: Context): Pair<ComponentName?, Int> { + try { + val sysUiPackageName = context.packageName + val pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo( + WindowConfiguration.WINDOWING_MODE_PINNED, + WindowConfiguration.ACTIVITY_TYPE_UNDEFINED + ) + if (pinnedTaskInfo?.childTaskIds != null && pinnedTaskInfo.childTaskIds.isNotEmpty()) { + for (i in pinnedTaskInfo.childTaskNames.indices.reversed()) { + val cn = ComponentName.unflattenFromString( + pinnedTaskInfo.childTaskNames[i] + ) + if (cn != null && cn.packageName != sysUiPackageName) { + return Pair(cn, pinnedTaskInfo.childTaskUserIds[i]) + } + } + } + } catch (e: RemoteException) { + ProtoLog.w( + ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Unable to get pinned stack.", TAG + ) + } + return Pair(null, 0) + } + + /** + * @return the pixels for a given dp value. + */ + @JvmStatic + fun dpToPx(dpValue: Float, dm: DisplayMetrics?): Int { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, dm).toInt() + } + + /** + * @return true if the aspect ratios differ + */ + @JvmStatic + fun aspectRatioChanged(aspectRatio1: Float, aspectRatio2: Float): Boolean { + return abs(aspectRatio1 - aspectRatio2) > EPSILON + } + + /** + * Checks whether title, description and intent match. + * Comparing icons would be good, but using equals causes false negatives + */ + @JvmStatic + fun remoteActionsMatch(action1: RemoteAction?, action2: RemoteAction?): Boolean { + if (action1 === action2) return true + if (action1 == null || action2 == null) return false + return action1.isEnabled == action2.isEnabled && + action1.shouldShowIcon() == action2.shouldShowIcon() && + action1.title == action2.title && + action1.contentDescription == action2.contentDescription && + action1.actionIntent == action2.actionIntent + } + + /** + * Returns true if the actions in the lists match each other according to + * [ ][PipUtils.remoteActionsMatch], including their position. + */ + @JvmStatic + fun remoteActionsChanged(list1: List<RemoteAction?>?, list2: List<RemoteAction?>?): Boolean { + if (list1 == null && list2 == null) { + return false + } + if (list1 == null || list2 == null) { + return true + } + if (list1.size != list2.size) { + return true + } + for (i in list1.indices) { + if (!remoteActionsMatch(list1[i], list2[i])) { + return true + } + } + return false + } + + /** @return [TaskSnapshot] for a given task id. + */ + @JvmStatic + fun getTaskSnapshot(taskId: Int, isLowResolution: Boolean): TaskSnapshot? { + return if (taskId <= 0) null else try { + ActivityTaskManager.getService().getTaskSnapshot( + taskId, isLowResolution, false /* takeSnapshotIfNeeded */ + ) + } catch (e: RemoteException) { + Log.e(TAG, "Failed to get task snapshot, taskId=$taskId", e) + null + } + } + + @JvmStatic + val isPip2ExperimentEnabled: Boolean + get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false) +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 24ef44a7c0f4..4e92ca113114 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -34,6 +34,7 @@ import com.android.wm.shell.common.pip.PhoneSizeSpecSource; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; @@ -50,7 +51,6 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransition; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PipController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java index 04032bb17fec..9c9364e17e0e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java @@ -18,9 +18,9 @@ package com.android.wm.shell.dagger.pip; import android.annotation.Nullable; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipUtils; import dagger.Module; import dagger.Provides; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 633f627e8e71..b0f75c6a1e6d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -311,7 +311,7 @@ class DesktopTasksController( ) val wct = WindowContainerTransaction() wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW) - wct.setBounds(task.token, null) + wct.setBounds(task.token, Rect()) wct.setDensityDpi(task.token, getDefaultDensityDpi()) if (Transitions.ENABLE_SHELL_TRANSITIONS) { transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index ac711eafe3ef..4fef672b2cd8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -28,6 +28,7 @@ import android.util.Size; import android.view.Gravity; import com.android.wm.shell.R; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import java.io.PrintWriter; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java index 456f85b52da4..4aa260b44646 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java @@ -16,7 +16,7 @@ package com.android.wm.shell.pip; -import static com.android.wm.shell.pip.PipUtils.dpToPx; +import static com.android.wm.shell.common.pip.PipUtils.dpToPx; import android.content.Context; import android.content.res.Resources; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 296857b196cf..ed9ff1c169c6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -83,6 +83,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index db7e2c0c529f..83e03dc850a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -64,6 +64,7 @@ import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 0f74f9e323a5..64bba672a5b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -38,6 +38,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.split.SplitScreenUtils; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java deleted file mode 100644 index 3cd9848d5686..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.pip; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.util.TypedValue.COMPLEX_UNIT_DIP; - -import android.annotation.Nullable; -import android.app.ActivityTaskManager; -import android.app.ActivityTaskManager.RootTaskInfo; -import android.app.RemoteAction; -import android.content.ComponentName; -import android.content.Context; -import android.os.RemoteException; -import android.os.SystemProperties; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.Pair; -import android.util.TypedValue; -import android.window.TaskSnapshot; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.protolog.ShellProtoLogGroup; - -import java.util.List; -import java.util.Objects; - -/** A class that includes convenience methods. */ -public class PipUtils { - private static final String TAG = "PipUtils"; - - // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal. - private static final double EPSILON = 1e-7; - - private static final String ENABLE_PIP2_IMPLEMENTATION = - "persist.wm.debug.enable_pip2_implementation"; - - /** - * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack. - * The component name may be null if no such activity exists. - */ - public static Pair<ComponentName, Integer> getTopPipActivity(Context context) { - try { - final String sysUiPackageName = context.getPackageName(); - final RootTaskInfo pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); - if (pinnedTaskInfo != null && pinnedTaskInfo.childTaskIds != null - && pinnedTaskInfo.childTaskIds.length > 0) { - for (int i = pinnedTaskInfo.childTaskNames.length - 1; i >= 0; i--) { - ComponentName cn = ComponentName.unflattenFromString( - pinnedTaskInfo.childTaskNames[i]); - if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) { - return new Pair<>(cn, pinnedTaskInfo.childTaskUserIds[i]); - } - } - } - } catch (RemoteException e) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Unable to get pinned stack.", TAG); - } - return new Pair<>(null, 0); - } - - /** - * @return the pixels for a given dp value. - */ - public static int dpToPx(float dpValue, DisplayMetrics dm) { - return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm); - } - - /** - * @return true if the aspect ratios differ - */ - public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) { - return Math.abs(aspectRatio1 - aspectRatio2) > EPSILON; - } - - /** - * Checks whether title, description and intent match. - * Comparing icons would be good, but using equals causes false negatives - */ - public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) { - if (action1 == action2) return true; - if (action1 == null || action2 == null) return false; - return action1.isEnabled() == action2.isEnabled() - && action1.shouldShowIcon() == action2.shouldShowIcon() - && Objects.equals(action1.getTitle(), action2.getTitle()) - && Objects.equals(action1.getContentDescription(), action2.getContentDescription()) - && Objects.equals(action1.getActionIntent(), action2.getActionIntent()); - } - - /** - * Returns true if the actions in the lists match each other according to {@link - * PipUtils#remoteActionsMatch(RemoteAction, RemoteAction)}, including their position. - */ - public static boolean remoteActionsChanged(List<RemoteAction> list1, List<RemoteAction> list2) { - if (list1 == null && list2 == null) { - return false; - } - if (list1 == null || list2 == null) { - return true; - } - if (list1.size() != list2.size()) { - return true; - } - for (int i = 0; i < list1.size(); i++) { - if (!remoteActionsMatch(list1.get(i), list2.get(i))) { - return true; - } - } - return false; - } - - /** @return {@link TaskSnapshot} for a given task id. */ - @Nullable - public static TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) { - if (taskId <= 0) return null; - try { - return ActivityTaskManager.getService().getTaskSnapshot( - taskId, isLowResolution, false /* takeSnapshotIfNeeded */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e); - return null; - } - } - - public static boolean isPip2ExperimentEnabled() { - return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 5c65d7839f07..ddea574c3c89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -77,6 +77,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.IPip; @@ -93,7 +94,6 @@ import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 837426ae3aab..fc34772f2fce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -67,7 +67,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUiEventLogger; -import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 6055fd9d9593..cf54a7163d04 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -51,13 +51,13 @@ import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java index f1606f6a8ca5..6b890c49b713 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java @@ -36,7 +36,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipMediaController; -import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.ArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 7c1563787a12..57439a59ccca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -55,7 +55,7 @@ import com.android.internal.widget.LinearLayoutManager; import com.android.internal.widget.RecyclerView; import com.android.wm.shell.R; import com.android.wm.shell.common.TvWindowMenuActionButton; -import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.List; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index 39c7a4b220a7..1c94625ddde9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -37,8 +37,8 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ImageUtils; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipMediaController; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipParamsChangedForwarder; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.List; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java index dbec607c2ae9..672080407658 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java @@ -26,6 +26,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; @@ -36,7 +37,6 @@ import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.splitscreen.SplitScreenController; import java.util.Objects; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS new file mode 100644 index 000000000000..ec09827fa4d1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS @@ -0,0 +1,3 @@ +# WM shell sub-module pip owner +hwwang@google.com +mateuszc@google.com diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt index 97147a3e1672..bc095bbacc5a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -31,6 +31,7 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.LaunchBubbleHelper +import com.android.server.wm.flicker.helpers.MultiWindowUtils import com.android.wm.shell.flicker.BaseTest import org.junit.runners.Parameterized @@ -56,6 +57,9 @@ abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) ): FlickerBuilder.() -> Unit { return { setup { + MultiWindowUtils.executeShellCommand( + instrumentation, + "settings put secure force_hide_bubbles_user_education 1") notifyManager.setBubblesAllowed( testApp.packageName, uid, @@ -67,6 +71,9 @@ abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) } teardown { + MultiWindowUtils.executeShellCommand( + instrumentation, + "settings put secure force_hide_bubbles_user_education 0") notifyManager.setBubblesAllowed( testApp.packageName, uid, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java index 139724f709c7..58d9a6486ff2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java @@ -203,6 +203,60 @@ public class BubblePositionerTest extends ShellTestCase { assertThat(restingPosition.y).isEqualTo(getDefaultYPosition()); } + /** Test that the default resting position on tablet is middle right. */ + @Test + public void testGetDefaultPosition_appBubble_onTablet() { + new WindowManagerConfig().setLargeScreen().setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + + assertThat(startPosition.x).isEqualTo(allowableStackRegion.right); + assertThat(startPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testGetRestingPosition_appBubble_onTablet_RTL() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + RectF allowableStackRegion = + mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */); + PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */); + + assertThat(startPosition.x).isEqualTo(allowableStackRegion.left); + assertThat(startPosition.y).isEqualTo(getDefaultYPosition()); + } + + @Test + public void testHasUserModifiedDefaultPosition_false() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + + mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition()); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + } + + @Test + public void testHasUserModifiedDefaultPosition_true() { + new WindowManagerConfig().setLargeScreen().setLayoutDirection( + LAYOUT_DIRECTION_RTL).setUpConfig(); + mPositioner.update(); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse(); + + mPositioner.setRestingPosition(new PointF(0, 100)); + + assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue(); + } + /** * Calculates the Y position bubbles should be placed based on the config. Based on * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 99a1ac663286..a57a7bf4c659 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -99,6 +99,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -1061,7 +1062,8 @@ public class ShellTransitionTests extends ShellTestCase { mTransactionPool, createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor); final RecentsTransitionHandler recentsHandler = - new RecentsTransitionHandler(shellInit, transitions, null); + new RecentsTransitionHandler(shellInit, transitions, + mock(RecentTasksController.class)); transitions.replaceDefaultHandlerForTest(mDefaultHandler); shellInit.init(); diff --git a/location/Android.bp b/location/Android.bp new file mode 100644 index 000000000000..46dca74e7e40 --- /dev/null +++ b/location/Android.bp @@ -0,0 +1,41 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +//location sources that will populate the new module +filegroup { + name: "framework-location-nonupdatable-sources", + srcs: [ + "placeholder_java/android/location/Placeholder.java", + ], +} + +java_sdk_library { + name: "framework-location", + srcs: [ + ":framework-location-nonupdatable-sources", + ], + defaults: ["framework-non-updatable-unbundled-defaults"], + permitted_packages: [ + "android.location", + "com.android.internal.location", + ], + libs: [ + "app-compat-annotations", + "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage + ], + hidden_api_packages: [ + "com.android.internal.location", + ], + aidl: { + include_dirs: [ + "frameworks/base/location/java", + "frameworks/base/core/java", + ], + }, +} diff --git a/location/api/current.txt b/location/api/current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/module-lib-current.txt b/location/api/module-lib-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/module-lib-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/module-lib-removed.txt b/location/api/module-lib-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/removed.txt b/location/api/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/system-current.txt b/location/api/system-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/system-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/system-removed.txt b/location/api/system-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/test-current.txt b/location/api/test-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/test-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/api/test-removed.txt b/location/api/test-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/location/api/test-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/location/placeholder_java/android/location/Placeholder.java b/location/placeholder_java/android/location/Placeholder.java new file mode 100644 index 000000000000..f0dbce829174 --- /dev/null +++ b/location/placeholder_java/android/location/Placeholder.java @@ -0,0 +1,27 @@ +/* + * 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.location; + +/** + * Placeholder class so new frameworks-location module isn't empty, will be removed once module is + * populated. + * + * @hide + * + */ +public class Placeholder { +} diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING index 5ae77b5a8e2f..a9da832b2a5a 100644 --- a/media/TEST_MAPPING +++ b/media/TEST_MAPPING @@ -37,6 +37,17 @@ } ], "file_patterns": ["(?i)drm|crypto"] + }, + { + "file_patterns": [ + "[^/]*(Ringtone)[^/]*\\.java" + ], + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "androidx.test.filters.LargeTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] } ] } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 0ff1b1e19811..9234479ea8fd 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -16,6 +16,7 @@ package android.media; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -35,19 +36,20 @@ import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.StaleDataException; import android.net.Uri; -import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.vibrator.persistence.VibrationXmlParser; import android.provider.BaseColumns; import android.provider.MediaStore; import android.provider.MediaStore.Audio.AudioColumns; import android.provider.MediaStore.MediaColumns; import android.provider.Settings; import android.provider.Settings.System; +import android.text.TextUtils; import android.util.Log; import com.android.internal.database.SortCursor; @@ -58,6 +60,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -209,21 +213,30 @@ public class RingtoneManager { */ public static final String EXTRA_RINGTONE_PICKED_URI = "android.intent.extra.ringtone.PICKED_URI"; - + + /** + * Declares the allowed types of media for this RingtoneManager. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "MEDIA_", value = { + Ringtone.MEDIA_SOUND, + Ringtone.MEDIA_VIBRATION, + }) + public @interface MediaType {} + // Make sure the column ordering and then ..._COLUMN_INDEX are in sync - private static final String[] INTERNAL_COLUMNS = new String[] { + private static final String[] MEDIA_AUDIO_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.TITLE_KEY, }; - private static final String[] MEDIA_COLUMNS = new String[] { - MediaStore.Audio.Media._ID, - MediaStore.Audio.Media.TITLE, - MediaStore.Audio.Media.TITLE, - MediaStore.Audio.Media.TITLE_KEY, + private static final String[] MEDIA_VIBRATION_COLUMNS = new String[]{ + MediaStore.Files.FileColumns._ID, + MediaStore.Files.FileColumns.TITLE, }; /** @@ -251,7 +264,9 @@ public class RingtoneManager { private Cursor mCursor; private int mType = TYPE_RINGTONE; - + @MediaType + private int mMediaType = Ringtone.MEDIA_SOUND; + /** * If a column (item from this list) exists in the Cursor, its value must * be true (value of 1) for the row to be returned. @@ -318,6 +333,41 @@ public class RingtoneManager { } /** + * Sets the media type that will be listed by the RingtoneManager. + * + * <p>This method should be called before calling {@link RingtoneManager#getCursor()}. + * + * @hide + */ + public void setMediaType(@MediaType int mediaType) { + if (mCursor != null) { + throw new IllegalStateException( + "Setting media should be done before calling getCursor()."); + } + + switch (mediaType) { + case Ringtone.MEDIA_SOUND: + case Ringtone.MEDIA_VIBRATION: + mMediaType = mediaType; + break; + default: + throw new IllegalArgumentException("Unsupported media type " + mediaType); + } + } + + /** + * Returns the RingtoneManagers media type. + * + * @return the media type. + * @see #setMediaType + * @hide + */ + @MediaType + public int getMediaType() { + return mMediaType; + } + + /** * Sets which type(s) of ringtones will be listed by this. * * @param type The type(s), one or more of {@link #TYPE_RINGTONE}, @@ -454,19 +504,19 @@ public class RingtoneManager { return mCursor; } - ArrayList<Cursor> ringtoneCursors = new ArrayList<Cursor>(); - ringtoneCursors.add(getInternalRingtones()); - ringtoneCursors.add(getMediaRingtones()); + ArrayList<Cursor> cursors = new ArrayList<>(); + + cursors.add(queryMediaStore(/* internal= */ true)); + cursors.add(queryMediaStore(/* internal= */ false)); if (mIncludeParentRingtones) { Cursor parentRingtonesCursor = getParentProfileRingtones(); if (parentRingtonesCursor != null) { - ringtoneCursors.add(parentRingtonesCursor); + cursors.add(parentRingtonesCursor); } } - - return mCursor = new SortCursor(ringtoneCursors.toArray(new Cursor[ringtoneCursors.size()]), - MediaStore.Audio.Media.DEFAULT_SORT_ORDER); + return mCursor = new SortCursor(cursors.toArray(new Cursor[cursors.size()]), + getSortOrderForMedia(mMediaType)); } private Cursor getParentProfileRingtones() { @@ -478,9 +528,7 @@ public class RingtoneManager { // We don't need to re-add the internal ringtones for the work profile since // they are the same as the personal profile. We just need the external // ringtones. - final Cursor res = getMediaRingtones(parentContext); - return new ExternalRingtonesCursorWrapper(res, ContentProvider.maybeAddUserId( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, parentInfo.id)); + return queryMediaStore(parentContext, /* internal= */ false); } } return null; @@ -502,7 +550,7 @@ public class RingtoneManager { Uri positionUri = getRingtoneUri(position); if (Ringtone.useRingtoneV2()) { mPreviousRingtone = new Ringtone.Builder( - mContext, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(mType)) + mContext, mMediaType, getDefaultAudioAttributes(mType)) .setUri(positionUri) .build(); } else { @@ -675,11 +723,13 @@ public class RingtoneManager { */ public static Uri getValidRingtoneUri(Context context) { final RingtoneManager rm = new RingtoneManager(context); - - Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones()); + + Uri uri = getValidRingtoneUriFromCursorAndClose(context, + rm.queryMediaStore(/* internal= */ true)); if (uri == null) { - uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); + uri = getValidRingtoneUriFromCursorAndClose(context, + rm.queryMediaStore(/* internal= */ false)); } return uri; @@ -700,28 +750,26 @@ public class RingtoneManager { } } - @UnsupportedAppUsage - private Cursor getInternalRingtones() { - final Cursor res = query( - MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns), - null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); - return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI); + private Cursor queryMediaStore(boolean internal) { + return queryMediaStore(mContext, internal); } - private Cursor getMediaRingtones() { - final Cursor res = getMediaRingtones(mContext); - return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI); - } + private Cursor queryMediaStore(Context context, boolean internal) { + Uri contentUri = getContentUriForMedia(mMediaType, internal); + String[] columns = + mMediaType == Ringtone.MEDIA_VIBRATION ? MEDIA_VIBRATION_COLUMNS + : MEDIA_AUDIO_COLUMNS; + String whereClause = getWhereClauseForMedia(mMediaType, mFilterColumns); + String sortOrder = getSortOrderForMedia(mMediaType); + + Cursor cursor = query(contentUri, columns, whereClause, /* selectionArgs= */ null, + sortOrder, context); - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private Cursor getMediaRingtones(Context context) { - // MediaStore now returns ringtones on other storage devices, even when - // we don't have storage or audio permissions - return query( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, - constructBooleanTrueWhereClause(mFilterColumns), null, - MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context); + if (context.getUserId() != mContext.getUserId()) { + contentUri = ContentProvider.maybeAddUserId(contentUri, context.getUserId()); + } + + return new ExternalRingtonesCursorWrapper(cursor, contentUri); } private void setFilterColumnsList(int type) { @@ -740,6 +788,56 @@ public class RingtoneManager { columns.add(MediaStore.Audio.AudioColumns.IS_ALARM); } } + + /** + * Returns the sort order for the specified media. + * + * @param media The RingtoneManager media type. + * @return The sort order column. + */ + private static String getSortOrderForMedia(@MediaType int media) { + return media == Ringtone.MEDIA_VIBRATION ? MediaStore.Files.FileColumns.TITLE + : MediaStore.Audio.Media.DEFAULT_SORT_ORDER; + } + + /** + * Returns the content URI based on the specified media and whether it's internal or external + * storage. + * + * @param media The RingtoneManager media type. + * @param internal Whether it's for internal or external storage. + * @return The media content URI. + */ + private static Uri getContentUriForMedia(@MediaType int media, boolean internal) { + switch (media) { + case Ringtone.MEDIA_VIBRATION: + return MediaStore.Files.getContentUri( + internal ? MediaStore.VOLUME_INTERNAL : MediaStore.VOLUME_EXTERNAL); + case Ringtone.MEDIA_SOUND: + return internal ? MediaStore.Audio.Media.INTERNAL_CONTENT_URI + : MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + default: + throw new IllegalArgumentException("Unsupported media type " + media); + } + } + + /** + * Constructs a where clause based on the media type. This will be used to find all matching + * sound or vibration files. + * + * @param media The RingtoneManager media type. + * @param columns The columns that must be true, when media type is {@link Ringtone#MEDIA_SOUND} + * @return The where clause. + */ + private static String getWhereClauseForMedia(@MediaType int media, List<String> columns) { + // TODO(b/296213309): Filtering by ringtone-type isn't supported yet for vibrations. + if (media == Ringtone.MEDIA_VIBRATION) { + return TextUtils.formatSimple("(%s='%s')", MediaStore.Files.FileColumns.MIME_TYPE, + VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE); + } + + return constructBooleanTrueWhereClause(columns); + } /** * Constructs a where clause that consists of at least one column being 1 @@ -769,14 +867,6 @@ public class RingtoneManager { return sb.toString(); } - - private Cursor query(Uri uri, - String[] projection, - String selection, - String[] selectionArgs, - String sortOrder) { - return query(uri, projection, selection, selectionArgs, sortOrder, mContext); - } private Cursor query(Uri uri, String[] projection, diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp new file mode 100644 index 000000000000..55b98c4704b1 --- /dev/null +++ b/media/tests/ringtone/Android.bp @@ -0,0 +1,30 @@ +package { + // See: http://go/android-license-faq + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "MediaRingtoneTests", + + srcs: ["src/**/*.java"], + + libs: [ + "android.test.runner", + "android.test.base", + ], + + static_libs: [ + "androidx.test.rules", + "testng", + "androidx.test.ext.truth", + "frameworks-base-testutils", + ], + + test_suites: [ + "device-tests", + "automotive-tests", + ], + + platform_apis: true, + certificate: "platform", +} diff --git a/media/tests/ringtone/AndroidManifest.xml b/media/tests/ringtone/AndroidManifest.xml new file mode 100644 index 000000000000..27eda07cd0d3 --- /dev/null +++ b/media/tests/ringtone/AndroidManifest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.framework.base.media.ringtone.tests"> + + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + + <activity android:name="MediaRingtoneTests" + android:label="Media Ringtone Tests" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.framework.base.media.ringtone.tests" + android:label="Media Ringtone Tests"/> +</manifest> diff --git a/media/tests/ringtone/TEST_MAPPING b/media/tests/ringtone/TEST_MAPPING new file mode 100644 index 000000000000..6f25c147076c --- /dev/null +++ b/media/tests/ringtone/TEST_MAPPING @@ -0,0 +1,20 @@ +{ + "presubmit": [ + { + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "androidx.test.filters.LargeTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ], + "postsubmit": [ + { + "name": "MediaRingtoneTests", + "options": [ + {"exclude-annotation": "org.junit.Ignore"} + ] + } + ] +}
\ No newline at end of file diff --git a/media/tests/ringtone/res/raw/test_haptic_file.ahv b/media/tests/ringtone/res/raw/test_haptic_file.ahv new file mode 100644 index 000000000000..18c99c79814f --- /dev/null +++ b/media/tests/ringtone/res/raw/test_haptic_file.ahv @@ -0,0 +1,17 @@ +<vibration> + <waveform-effect> + <waveform-entry durationMs="63" amplitude="255"/> + <waveform-entry durationMs="63" amplitude="231"/> + <waveform-entry durationMs="63" amplitude="208"/> + <waveform-entry durationMs="63" amplitude="185"/> + <waveform-entry durationMs="63" amplitude="162"/> + <waveform-entry durationMs="63" amplitude="139"/> + <waveform-entry durationMs="63" amplitude="115"/> + <waveform-entry durationMs="63" amplitude="92"/> + <waveform-entry durationMs="63" amplitude="69"/> + <waveform-entry durationMs="63" amplitude="46"/> + <waveform-entry durationMs="63" amplitude="23"/> + <waveform-entry durationMs="63" amplitude="0"/> + <waveform-entry durationMs="1250" amplitude="0"/> + </waveform-effect> +</vibration> diff --git a/media/tests/ringtone/res/raw/test_sound_file.mp3 b/media/tests/ringtone/res/raw/test_sound_file.mp3 Binary files differnew file mode 100644 index 000000000000..c1b2fdf93991 --- /dev/null +++ b/media/tests/ringtone/res/raw/test_sound_file.mp3 diff --git a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java new file mode 100644 index 000000000000..a92b29883ce7 --- /dev/null +++ b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 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.media; + +import static com.google.android.mms.ContentType.AUDIO_MP3; +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.os.vibrator.persistence.VibrationXmlParser; +import android.provider.MediaStore; +import android.text.TextUtils; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.framework.base.media.ringtone.tests.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(Parameterized.class) +public class RingtoneManagerTest { + @RingtoneManager.MediaType + private final int mMediaType; + private final List<Uri> mAddedFilesUri; + private Context mContext; + private RingtoneManager mRingtoneManager; + private long mTimestamp; + + @Parameterized.Parameters(name = "media = {0}") + public static Iterable<?> data() { + return Arrays.asList(Ringtone.MEDIA_SOUND, Ringtone.MEDIA_VIBRATION); + } + + public RingtoneManagerTest(@RingtoneManager.MediaType int mediaType) { + mMediaType = mediaType; + mAddedFilesUri = new ArrayList<>(); + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mTimestamp = SystemClock.uptimeMillis(); + mRingtoneManager = new RingtoneManager(mContext); + mRingtoneManager.setMediaType(mMediaType); + } + + @After + public void tearDown() { + // Clean up media store + for (Uri fileUri : mAddedFilesUri) { + mContext.getContentResolver().delete(fileUri, null); + } + } + + @Test + public void testSetMediaType_withValidValue_setsMediaCorrectly() { + mRingtoneManager.setMediaType(mMediaType); + assertThat(mRingtoneManager.getMediaType()).isEqualTo(mMediaType); + } + + @Test + public void testSetMediaType_withInvalidValue_throwsException() { + assertThrows(IllegalArgumentException.class, () -> mRingtoneManager.setMediaType(999)); + } + + @Test + public void testSetMediaType_afterCallingGetCursor_throwsException() { + mRingtoneManager.getCursor(); + assertThrows(IllegalStateException.class, () -> mRingtoneManager.setMediaType(mMediaType)); + } + + @Test + public void testGetRingtone_ringtoneHasCorrectTitle() throws Exception { + String fileName = generateUniqueFileName("new_file"); + Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName); + + assertThat(ringtone.getTitle(mContext)).isEqualTo(fileName); + } + + @Test + public void testGetRingtone_ringtoneCanBePlayedAndStopped() throws Exception { + //TODO(b/261571543) Remove this assumption once we support playing vibrations. + assumeTrue(mMediaType == Ringtone.MEDIA_SOUND); + String fileName = generateUniqueFileName("new_file"); + Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName); + + ringtone.play(); + assertThat(ringtone.isPlaying()).isTrue(); + + ringtone.stop(); + assertThat(ringtone.isPlaying()).isFalse(); + } + + @Test + public void testGetCursor_withDifferentMedia_returnsCorrectCursor() throws Exception { + RingtoneManager audioRingtoneManager = new RingtoneManager(mContext); + String audioFileName = generateUniqueFileName("ringtone"); + addNewRingtoneToMediaStore(audioRingtoneManager, audioFileName); + + RingtoneManager vibrationRingtoneManager = new RingtoneManager(mContext); + vibrationRingtoneManager.setMediaType(Ringtone.MEDIA_VIBRATION); + String vibrationFileName = generateUniqueFileName("vibration"); + addNewRingtoneToMediaStore(vibrationRingtoneManager, vibrationFileName); + + Cursor audioCursor = audioRingtoneManager.getCursor(); + Cursor vibrationCursor = vibrationRingtoneManager.getCursor(); + + List<String> audioTitles = extractRecordTitles(audioCursor); + List<String> vibrationTitles = extractRecordTitles(vibrationCursor); + + assertThat(audioTitles).contains(audioFileName); + assertThat(audioTitles).doesNotContain(vibrationFileName); + + assertThat(vibrationTitles).contains(vibrationFileName); + assertThat(vibrationTitles).doesNotContain(audioFileName); + } + + private List<String> extractRecordTitles(Cursor cursor) { + List<String> titles = new ArrayList<>(); + + if (cursor.moveToFirst()) { + do { + String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX); + titles.add(title); + } while (cursor.moveToNext()); + } + + return titles; + } + + private Ringtone addNewRingtoneToMediaStore(RingtoneManager ringtoneManager, String fileName) + throws Exception { + Uri fileUri = ringtoneManager.getMediaType() == Ringtone.MEDIA_SOUND ? addAudioFile( + fileName) : addVibrationFile(fileName); + mAddedFilesUri.add(fileUri); + + int ringtonePosition = ringtoneManager.getRingtonePosition(fileUri); + Ringtone ringtone = ringtoneManager.getRingtone(ringtonePosition); + // Validate this is the expected ringtone. + assertThat(ringtone.getUri()).isEqualTo(fileUri); + return ringtone; + } + + private Uri addAudioFile(String fileName) throws Exception { + ContentResolver resolver = mContext.getContentResolver(); + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName + ".mp3"); + contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_RINGTONES); + contentValues.put(MediaStore.Audio.Media.MIME_TYPE, AUDIO_MP3); + contentValues.put(MediaStore.Audio.Media.TITLE, fileName); + contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, 1); + + Uri contentUri = resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + contentValues); + writeRawDataToFile(resolver, contentUri, R.raw.test_sound_file); + + return resolver.canonicalizeOrElse(contentUri); + } + + private Uri addVibrationFile(String fileName) throws Exception { + ContentResolver resolver = mContext.getContentResolver(); + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, fileName + ".ahv"); + contentValues.put(MediaStore.Files.FileColumns.RELATIVE_PATH, + Environment.DIRECTORY_DOWNLOADS); + contentValues.put(MediaStore.Files.FileColumns.MIME_TYPE, + VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE); + contentValues.put(MediaStore.Files.FileColumns.TITLE, fileName); + + Uri contentUri = resolver.insert(MediaStore.Files.getContentUri(MediaStore + .VOLUME_EXTERNAL), contentValues); + writeRawDataToFile(resolver, contentUri, R.raw.test_haptic_file); + + return resolver.canonicalizeOrElse(contentUri); + } + + private void writeRawDataToFile(ContentResolver resolver, Uri contentUri, int rawResource) + throws Exception { + try (ParcelFileDescriptor pfd = + resolver.openFileDescriptor(contentUri, "w", null)) { + InputStream inputStream = mContext.getResources().openRawResource(rawResource); + FileOutputStream outputStream = new FileOutputStream(pfd.getFileDescriptor()); + outputStream.write(inputStream.readAllBytes()); + + inputStream.close(); + outputStream.flush(); + outputStream.close(); + + } catch (Exception e) { + throw new Exception("Failed to write data to file", e); + } + } + + private String generateUniqueFileName(String prefix) { + return TextUtils.formatSimple("%s_%d", prefix, mTimestamp); + } + +} diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index c571b742d2d1..c25df6e08fd0 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -144,8 +144,6 @@ APerformanceHintSession* APerformanceHintManager::createSession( binder::Status ret = mHintManager->createHintSession(mToken, tids, initialTargetWorkDurationNanos, &session); if (!ret.isOk() || !session) { - ALOGE("%s: PerformanceHint cannot create hint session. %s", __FUNCTION__, - ret.exceptionMessage().c_str()); return nullptr; } return new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 80cf6c313316..20740dcedf95 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -223,6 +223,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR); VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.WEAR_TTS_PREWARM_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF, BOOLEAN_VALIDATOR); VALIDATORS.put(System.UNREAD_NOTIFICATION_DOT_INDICATOR, BOOLEAN_VALIDATOR); VALIDATORS.put(System.AUTO_LAUNCH_MEDIA_CONTROLS, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 55527455ca11..0a98032e26d8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -845,6 +845,7 @@ final class SettingsState { private void doWriteState() { boolean wroteState = false; + String settingFailedToBePersisted = null; final int version; final ArrayMap<String, Setting> settings; final ArrayMap<String, String> namespaceBannedHashes; @@ -895,8 +896,14 @@ final class SettingsState { } } } catch (IOException ex) { - Slog.e(LOG_TAG, "[SKIPPED PERSISTING]" + setting.getName() + Slog.e(LOG_TAG, "[ABORT PERSISTING]" + setting.getName() + " due to error writing to disk", ex); + // A setting failed to be written. Abort the serialization to avoid leaving + // a partially serialized setting on disk, which can cause parsing errors. + // Note down the problematic setting, so that we can delete it before trying + // again to persist the rest of the settings. + settingFailedToBePersisted = setting.getName(); + throw ex; } } serializer.endTag(null, TAG_SETTINGS); @@ -922,14 +929,14 @@ final class SettingsState { Slog.i(LOG_TAG, "[PERSIST END]"); } } catch (Throwable t) { - Slog.wtf(LOG_TAG, "Failed to write settings, restoring backup", t); + Slog.wtf(LOG_TAG, "Failed to write settings, restoring old file", t); if (t instanceof IOException) { - if (DEBUG) { - // we failed to create a directory, so log the permissions and existence - // state for the settings file and directory - logSettingsDirectoryInformation(destination.getBaseFile()); - } if (t.getMessage().contains("Couldn't create directory")) { + if (DEBUG) { + // we failed to create a directory, so log the permissions and existence + // state for the settings file and directory + logSettingsDirectoryInformation(destination.getBaseFile()); + } // attempt to create the directory with Files.createDirectories, which // throws more informative errors than File.mkdirs. Path parentPath = destination.getBaseFile().getParentFile().toPath(); @@ -950,7 +957,15 @@ final class SettingsState { } } - if (wroteState) { + if (!wroteState) { + if (settingFailedToBePersisted != null) { + synchronized (mLock) { + // Delete the problematic setting. This will schedule a write as well. + deleteSettingLocked(settingFailedToBePersisted); + } + } + } else { + // success synchronized (mLock) { addHistoricalOperationLocked(HISTORICAL_OPERATION_PERSIST, null); } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c697c1ff66ec..203efbf76658 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -103,6 +103,7 @@ public class SettingsBackupTest { Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities Settings.System.SCREEN_BRIGHTNESS_FLOAT, Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, + Settings.System.WEAR_TTS_PREWARM_ENABLED, Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific ); diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml index 81b3152375ff..1f5765465075 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/styles.xml @@ -16,6 +16,10 @@ --> <resources> + <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight"> + <item name="android:windowLightStatusBar">false</item> + </style> + <!--Adds the theme to support SnackBar component and user configurable theme. --> <style name="ServiceTheme" parent="android:Theme.DeviceDefault.DayNight"> <item name="android:colorControlNormal">@color/colorControlNormal</item> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml index 2009cd1df4e5..a2508cdf4f16 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/styles.xml @@ -17,7 +17,9 @@ <resources> <!--The theme is for preference CollapsingToolbarBaseActivity settings--> - <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight" /> + <style name="AccessibilityMenuSettings" parent="android:Theme.DeviceDefault.DayNight"> + <item name="android:windowLightStatusBar">true</item> + </style> <!--Adds the theme to support SnackBar component and user configurable theme. --> <style name="ServiceTheme" parent="android:Theme.DeviceDefault.Light"> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index da48762e1960..0a100babde75 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -14,26 +14,20 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.composable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import android.view.View +import android.view.ViewGroup import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.SceneScope -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.qualifiers.KeyguardRootView import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -42,6 +36,7 @@ import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -54,6 +49,7 @@ class LockscreenScene constructor( @Application private val applicationScope: CoroutineScope, private val viewModel: LockscreenSceneViewModel, + @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View, ) : ComposableScene { override val key = SceneKey.Lockscreen @@ -72,6 +68,7 @@ constructor( ) { LockscreenScene( viewModel = viewModel, + viewProvider = viewProvider, modifier = modifier, ) } @@ -89,25 +86,22 @@ constructor( @Composable private fun LockscreenScene( viewModel: LockscreenSceneViewModel, + viewProvider: () -> View, modifier: Modifier = Modifier, ) { - // TODO(b/280879610): implement the real UI. - - val lockButtonIcon: Icon by viewModel.lockButtonIcon.collectAsState() - - Box(modifier = modifier) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.align(Alignment.Center) - ) { - Text("Lockscreen", style = MaterialTheme.typography.headlineMedium) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Button(onClick = { viewModel.onLockButtonClicked() }) { Icon(lockButtonIcon) } - - Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") } + AndroidView( + factory = { _ -> + val keyguardRootView = viewProvider() + // Remove the KeyguardRootView from any parent it might already have in legacy code just + // in case (a view can't have two parents). + (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView) + keyguardRootView + }, + update = { keyguardRootView -> + keyguardRootView.requireViewById<View>(R.id.lock_icon_view).setOnClickListener { + viewModel.onLockButtonClicked() } - } - } + }, + modifier = modifier, + ) } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index ab0225d63ac5..966e183fc821 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -51,15 +51,18 @@ import kotlinx.coroutines.withContext private val KEY_TIMESTAMP = "appliedTimestamp" private val KNOWN_PLUGINS = mapOf<String, List<ClockMetadata>>( - "com.android.systemui.falcon.one" to listOf(ClockMetadata("ANALOG_CLOCK_BIGNUM")), - "com.android.systemui.falcon.two" to listOf(ClockMetadata("DIGITAL_CLOCK_CALLIGRAPHY")), - "com.android.systemui.falcon.three" to listOf(ClockMetadata("DIGITAL_CLOCK_FLEX")), - "com.android.systemui.falcon.four" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")), - "com.android.systemui.falcon.five" to listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")), - "com.android.systemui.falcon.six" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")), - "com.android.systemui.falcon.seven" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")), - "com.android.systemui.falcon.eight" to listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")), - "com.android.systemui.falcon.nine" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")), + "com.android.systemui.clocks.bignum" to listOf(ClockMetadata("ANALOG_CLOCK_BIGNUM")), + "com.android.systemui.clocks.calligraphy" to + listOf(ClockMetadata("DIGITAL_CLOCK_CALLIGRAPHY")), + "com.android.systemui.clocks.flex" to listOf(ClockMetadata("DIGITAL_CLOCK_FLEX")), + "com.android.systemui.clocks.growth" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")), + "com.android.systemui.clocks.handwritten" to + listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")), + "com.android.systemui.clocks.inflate" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")), + "com.android.systemui.clocks.metro" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")), + "com.android.systemui.clocks.numoverlap" to + listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")), + "com.android.systemui.clocks.weather" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")), ) private fun <TKey : Any, TVal : Any> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut( diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 92083b0ba1b9..785177ed0f51 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -292,6 +292,7 @@ -packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt -packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt -packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt +-packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt -packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt -packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt -packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt diff --git a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml b/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml new file mode 100644 index 000000000000..a8abd793bd00 --- /dev/null +++ b/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" /> + <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" /> + <item android:color="@color/transparent" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml index a8c034986425..47a2965bcfac 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml @@ -32,6 +32,12 @@ <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="@color/qs_footer_power_button_overlay_color"/> + <corners android:radius="@dimen/qs_footer_action_corner_radius"/> + </shape> + </item> </ripple> </inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_pin_content_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_pin_content_view.xml new file mode 100644 index 000000000000..24222f7642be --- /dev/null +++ b/packages/SystemUI/res/layout-land/auth_credential_password_pin_content_view.xml @@ -0,0 +1,101 @@ +<!-- + ~ Copyright (C) 2019 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. + --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <RelativeLayout + android:id="@+id/auth_credential_header" + style="?headerStyle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"> + + <ImageView + android:id="@+id/icon" + style="?headerIconStyle" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:contentDescription="@null"/> + + <TextView + android:id="@+id/title" + style="?titleTextAppearance" + android:layout_below="@id/icon" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/subtitle" + style="?subTitleTextAppearance" + android:layout_below="@id/title" + android:layout_alignParentLeft="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/description" + style="?descriptionTextAppearance" + android:layout_below="@id/subtitle" + android:layout_alignParentLeft="true" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </RelativeLayout> + + <FrameLayout + android:id="@+id/auth_credential_input" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="vertical"> + + <ImeAwareEditText + android:id="@+id/lockPassword" + style="?passwordTextAppearance" + android:layout_width="208dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" + android:inputType="textPassword" + android:minHeight="48dp"/> + + <TextView + android:id="@+id/error" + style="?errorTextAppearance" + android:layout_gravity="center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </LinearLayout> + + <Button + android:id="@+id/emergencyCallButton" + style="@style/AuthCredentialEmergencyButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:text="@string/work_challenge_emergency_button_text"/> + </FrameLayout> + +</merge> diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml index e439f775f8ea..8ac7583088f9 100644 --- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml @@ -23,84 +23,6 @@ android:elevation="@dimen/biometric_dialog_elevation" android:theme="?app:attr/lockPinPasswordStyle"> - <RelativeLayout - android:id="@+id/auth_credential_header" - style="?headerStyle" - android:layout_width="wrap_content" - android:layout_height="match_parent"> - - <ImageView - android:id="@+id/icon" - style="?headerIconStyle" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:contentDescription="@null"/> - - <TextView - android:id="@+id/title" - style="?titleTextAppearance" - android:layout_below="@id/icon" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - <TextView - android:id="@+id/subtitle" - style="?subTitleTextAppearance" - android:layout_below="@id/title" - android:layout_alignParentLeft="true" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - <TextView - android:id="@+id/description" - style="?descriptionTextAppearance" - android:layout_below="@id/subtitle" - android:layout_alignParentLeft="true" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - </RelativeLayout> - - <FrameLayout - android:id="@+id/auth_credential_input" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal|top" - android:orientation="vertical"> - - <ImeAwareEditText - android:id="@+id/lockPassword" - style="?passwordTextAppearance" - android:layout_width="208dp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" - android:inputType="textPassword" - android:minHeight="48dp"/> - - <TextView - android:id="@+id/error" - style="?errorTextAppearance" - android:layout_gravity="center_horizontal" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> - </LinearLayout> - - <Button - android:id="@+id/emergencyCallButton" - style="@style/AuthCredentialEmergencyButtonStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:layout_gravity="center_horizontal|bottom" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:text="@string/work_challenge_emergency_button_text"/> - </FrameLayout> + <include layout="@layout/auth_credential_password_pin_content_view" /> </com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/auth_credential_pin_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pin_view.xml new file mode 100644 index 000000000000..8ac7583088f9 --- /dev/null +++ b/packages/SystemUI/res/layout-land/auth_credential_pin_view.xml @@ -0,0 +1,28 @@ +<!-- + ~ Copyright (C) 2020 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. + --> + +<com.android.systemui.biometrics.ui.CredentialPasswordView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:elevation="@dimen/biometric_dialog_elevation" + android:theme="?app:attr/lockPinPasswordStyle"> + + <include layout="@layout/auth_credential_password_pin_content_view" /> + +</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_password_pin_content_view.xml b/packages/SystemUI/res/layout/auth_credential_password_pin_content_view.xml new file mode 100644 index 000000000000..11284fd2237b --- /dev/null +++ b/packages/SystemUI/res/layout/auth_credential_password_pin_content_view.xml @@ -0,0 +1,104 @@ +<!-- + ~ Copyright (C) 2019 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. + --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <RelativeLayout + android:id="@+id/auth_credential_header" + style="?headerStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="0dp"> + + <ImageView + android:id="@+id/icon" + style="?headerIconStyle" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:contentDescription="@null" /> + + <TextView + android:id="@+id/title" + style="?titleTextAppearance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/icon" /> + + <TextView + android:id="@+id/subtitle" + style="?subTitleTextAppearance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/title" /> + + <TextView + android:id="@+id/description" + style="?descriptionTextAppearance" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/subtitle" /> + + </RelativeLayout> + + </ScrollView> + + <FrameLayout + android:id="@+id/auth_credential_input" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="vertical"> + + <ImeAwareEditText + android:id="@+id/lockPassword" + style="?passwordTextAppearance" + android:layout_width="208dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" + android:inputType="textPassword" + android:minHeight="48dp"/> + + <TextView + android:id="@+id/error" + style="?errorTextAppearance" + android:layout_gravity="center_horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + </LinearLayout> + + <Button + android:id="@+id/emergencyCallButton" + style="@style/AuthCredentialEmergencyButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layout_gravity="center_horizontal|bottom" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:text="@string/work_challenge_emergency_button_text"/> + </FrameLayout> + +</merge> diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml index 9336845f20f7..f8d9a87d5e54 100644 --- a/packages/SystemUI/res/layout/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -23,88 +23,6 @@ android:orientation="vertical" android:theme="?app:attr/lockPinPasswordStyle"> - <ScrollView - android:id="@+id/auth_credential_header" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <RelativeLayout - style="?headerStyle" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <ImageView - android:id="@+id/icon" - style="?headerIconStyle" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:contentDescription="@null" /> - - <TextView - android:id="@+id/title" - style="?titleTextAppearance" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/icon" /> - - <TextView - android:id="@+id/subtitle" - style="?subTitleTextAppearance" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/title" /> - - <TextView - android:id="@+id/description" - style="?descriptionTextAppearance" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/subtitle" /> - - </RelativeLayout> - - </ScrollView> - - <FrameLayout - android:id="@+id/auth_credential_input" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal|top" - android:orientation="vertical"> - - <ImeAwareEditText - android:id="@+id/lockPassword" - style="?passwordTextAppearance" - android:layout_width="208dp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" - android:inputType="textPassword" - android:minHeight="48dp"/> - - <TextView - android:id="@+id/error" - style="?errorTextAppearance" - android:layout_gravity="center_horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - </LinearLayout> - - <Button - android:id="@+id/emergencyCallButton" - style="@style/AuthCredentialEmergencyButtonStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:layout_gravity="center_horizontal|bottom" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:text="@string/work_challenge_emergency_button_text"/> - </FrameLayout> + <include layout="@layout/auth_credential_password_pin_content_view" /> </com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_pin_view.xml b/packages/SystemUI/res/layout/auth_credential_pin_view.xml new file mode 100644 index 000000000000..a1cf807af088 --- /dev/null +++ b/packages/SystemUI/res/layout/auth_credential_pin_view.xml @@ -0,0 +1,28 @@ +<!-- + ~ Copyright (C) 2019 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. + --> + +<com.android.systemui.biometrics.ui.CredentialPasswordView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:elevation="@dimen/biometric_dialog_elevation" + android:orientation="vertical" + android:theme="?app:attr/lockPinPasswordStyle"> + + <include layout="@layout/auth_credential_password_pin_content_view" /> + +</com.android.systemui.biometrics.ui.CredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 4e72518bc613..04eae64013b3 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -222,4 +222,13 @@ <!-- Communal mode --> <item type="id" name="communal_widget_wrapper" /> + + <!-- Values assigned to the views in Biometrics Prompt --> + <item type="id" name="pin_pad"/> + + <!-- + Used to tag views programmatically added to the smartspace area so they can be more easily + removed later. + --> + <item type="id" name="tag_smartspace_view" /> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 4bc949116807..33e453cf4354 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -76,26 +76,11 @@ oneway interface IOverviewProxy { void onSystemBarAttributesChanged(int displayId, int behavior) = 20; /** - * Sent when screen turned on and ready to use (blocker scrim is hidden) - */ - void onScreenTurnedOn() = 21; - - /** * Sent when the desired dark intensity of the nav buttons has changed */ void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22; /** - * Sent when screen started turning on. - */ - void onScreenTurningOn() = 23; - - /** - * Sent when screen started turning off. - */ - void onScreenTurningOff() = 24; - - /** * Sent when split keyboard shortcut is triggered to enter stage split. */ void enterStageSplitFromRunningApp(boolean leftOrTop) = 25; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index cab54d08b3ec..8200e5c84186 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -39,6 +39,7 @@ import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; +import android.os.SystemProperties; import android.provider.Settings; import android.util.Log; import android.view.HapticFeedbackConstants; @@ -76,6 +77,8 @@ public class RotationButtonController { private static final String TAG = "RotationButtonController"; private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100; private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000; + private static final boolean OEM_DISALLOW_ROTATION_IN_SUW = + SystemProperties.getBoolean("ro.setupwizard.rotation_locked", false); private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3; @@ -375,6 +378,12 @@ public class RotationButtonController { } public void onRotationProposal(int rotation, boolean isValid) { + boolean isUserSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; + if (!isUserSetupComplete && OEM_DISALLOW_ROTATION_IN_SUW) { + return; + } + int windowRotation = mWindowRotationProvider.get(); if (!mRotationButton.acceptRotationProposal()) { @@ -497,8 +506,7 @@ public class RotationButtonController { boolean canShowRotationButton() { return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT - || isGesturalMode(mNavBarMode) - || mTaskBarVisible; + || isGesturalMode(mNavBarMode); } @DrawableRes diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt index 899cad89a0be..006974c9fa92 100644 --- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt +++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt @@ -66,7 +66,7 @@ constructor( window.isNavigationBarContrastEnforced = false window.navigationBarColor = Color.TRANSPARENT - clock = findViewById(R.id.clock) + clock = requireViewById(R.id.clock) keyguardStatusViewController = keyguardStatusViewComponentFactory.build(clock).keyguardStatusViewController.apply { setDisplayedOnSecondaryDisplay() diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 05ace74306bc..6d2880e00203 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -293,7 +293,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS int viewIndex = mStatusArea.indexOfChild(ksv); ksv.setVisibility(View.GONE); - mSmartspaceController.removeViewsFromParent(mStatusArea); + removeViewsFromStatusArea(); addSmartspaceView(); // TODO(b/261757708): add content observer for the Settings toggle and add/remove // weather according to the Settings. @@ -325,7 +325,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS void onLocaleListChanged() { if (mSmartspaceController.isEnabled()) { - mSmartspaceController.removeViewsFromParent(mStatusArea); + removeViewsFromStatusArea(); addSmartspaceView(); if (mSmartspaceController.isDateWeatherDecoupled()) { mDateWeatherView.removeView(mWeatherView); @@ -620,4 +620,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS return ((mCurrentClockSize == LARGE) ? clock.getLargeClock() : clock.getSmallClock()) .getConfig().getHasCustomWeatherDataDisplay(); } + + private void removeViewsFromStatusArea() { + for (int i = mStatusArea.getChildCount() - 1; i >= 0; i--) { + final View childView = mStatusArea.getChildAt(i); + if (childView.getTag(R.id.tag_smartspace_view) != null) { + mStatusArea.removeViewAt(i); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 802a550c4d29..7464c8803c99 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -499,6 +499,8 @@ public class AuthContainerView extends LinearLayout R.layout.auth_credential_pattern_view, null, false); break; case Utils.CREDENTIAL_PIN: + mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false); + break; case Utils.CREDENTIAL_PASSWORD: mCredentialView = factory.inflate( R.layout.auth_credential_password_view, null, false); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt index 20c3e4098e83..f7f910391566 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt @@ -56,10 +56,10 @@ interface UdfpsModule { ) ) } else { - BoundingBoxOverlapDetector() + BoundingBoxOverlapDetector(values[2]) } } else { - return BoundingBoxOverlapDetector() + return BoundingBoxOverlapDetector(1f) } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt index cf6044f146b0..9b946db0daf0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt @@ -17,16 +17,30 @@ package com.android.systemui.biometrics.udfps import android.graphics.Rect +import android.os.Build +import android.util.Log import com.android.systemui.dagger.SysUISingleton /** Returns whether the touch coordinates are within the sensor's bounding box. */ @SysUISingleton -class BoundingBoxOverlapDetector : OverlapDetector { +class BoundingBoxOverlapDetector(private val targetSize: Float) : OverlapDetector { + + private val TAG = "BoundingBoxOverlapDetector" + override fun isGoodOverlap( touchData: NormalizedTouchData, nativeSensorBounds: Rect, nativeOverlayBounds: Rect, - ): Boolean = - touchData.isWithinBounds(nativeOverlayBounds) && - touchData.isWithinBounds(nativeSensorBounds) + ): Boolean { + val scaledRadius = (nativeSensorBounds.width() / 2) * targetSize + val scaledSensorBounds = + Rect( + (nativeSensorBounds.centerX() - scaledRadius).toInt(), + (nativeSensorBounds.centerY() - scaledRadius).toInt(), + (nativeSensorBounds.centerX() + scaledRadius).toInt(), + (nativeSensorBounds.centerY() + scaledRadius).toInt(), + ) + + return touchData.isWithinBounds(scaledSensorBounds) + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt index 709fe855fbfc..6c5cc4835787 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt @@ -8,11 +8,8 @@ import android.view.View import android.view.WindowInsets import android.view.WindowInsets.Type.ime import android.view.accessibility.AccessibilityManager -import android.widget.ImageView -import android.widget.ImeAwareEditText import android.widget.LinearLayout import android.widget.TextView -import androidx.core.view.isGone import com.android.systemui.R import com.android.systemui.biometrics.AuthPanelController import com.android.systemui.biometrics.ui.binder.CredentialViewBinder @@ -22,14 +19,6 @@ import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel class CredentialPasswordView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs), CredentialView, View.OnApplyWindowInsetsListener { - private lateinit var titleView: TextView - private lateinit var subtitleView: TextView - private lateinit var descriptionView: TextView - private lateinit var iconView: ImageView - private lateinit var passwordField: ImeAwareEditText - private lateinit var credentialHeader: View - private lateinit var credentialInput: View - private var bottomInset: Int = 0 private val accessibilityManager by lazy { @@ -48,90 +37,32 @@ class CredentialPasswordView(context: Context, attrs: AttributeSet?) : override fun onFinishInflate() { super.onFinishInflate() - - titleView = requireViewById(R.id.title) - subtitleView = requireViewById(R.id.subtitle) - descriptionView = requireViewById(R.id.description) - iconView = requireViewById(R.id.icon) - passwordField = requireViewById(R.id.lockPassword) - credentialHeader = requireViewById(R.id.auth_credential_header) - credentialInput = requireViewById(R.id.auth_credential_input) - setOnApplyWindowInsetsListener(this) } - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - super.onLayout(changed, left, top, right, bottom) - - val inputLeftBound: Int - var inputTopBound: Int - var headerRightBound = right - var headerTopBounds = top - var headerBottomBounds = bottom - val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom - val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom - if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) { - inputTopBound = (bottom - credentialInput.height) / 2 - inputLeftBound = (right - left) / 2 - headerRightBound = inputLeftBound - if (descriptionView.bottom > headerBottomBounds) { - headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset) - credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom) - } - } else { - inputTopBound = descBottom + (bottom - descBottom - credentialInput.height) / 2 - inputLeftBound = (right - left - credentialInput.width) / 2 - - if (bottom - inputTopBound < credentialInput.height) { - inputTopBound = bottom - credentialInput.height - } - - if (descriptionView.bottom > inputTopBound) { - credentialHeader.layout(left, headerTopBounds, headerRightBound, inputTopBound) - } - } - - credentialInput.layout(inputLeftBound, inputTopBound, right, bottom) - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - - val newWidth = MeasureSpec.getSize(widthMeasureSpec) - val newHeight = MeasureSpec.getSize(heightMeasureSpec) - bottomInset - - setMeasuredDimension(newWidth, newHeight) - - val halfWidthSpec = MeasureSpec.makeMeasureSpec(width / 2, MeasureSpec.AT_MOST) - val fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED) - if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) { - measureChildren(halfWidthSpec, fullHeightSpec) - } else { - measureChildren(widthMeasureSpec, fullHeightSpec) - } - } - override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { - val bottomInsets = insets.getInsets(ime()) - if (bottomInset != bottomInsets.bottom) { - bottomInset = bottomInsets.bottom - - if (bottomInset > 0 && resources.configuration.orientation == ORIENTATION_LANDSCAPE) { - titleView.isSingleLine = true - titleView.ellipsize = TextUtils.TruncateAt.MARQUEE - titleView.marqueeRepeatLimit = -1 - // select to enable marquee unless a screen reader is enabled - titleView.isSelected = accessibilityManager?.shouldMarquee() ?: false - } else { - titleView.isSingleLine = false - titleView.ellipsize = null - // select to enable marquee unless a screen reader is enabled - titleView.isSelected = false + val imeBottomInset = insets.getInsets(ime()).bottom + if (bottomInset != imeBottomInset) { + val titleView: TextView? = findViewById(R.id.title) + if (titleView != null) { + if ( + bottomInset > 0 && resources.configuration.orientation == ORIENTATION_LANDSCAPE + ) { + titleView.isSingleLine = true + titleView.ellipsize = TextUtils.TruncateAt.MARQUEE + titleView.marqueeRepeatLimit = -1 + // select to enable marquee unless a screen reader is enabled + titleView.isSelected = accessibilityManager.shouldMarquee() + } else { + titleView.isSingleLine = false + titleView.ellipsize = null + // select to enable marquee unless a screen reader is enabled + titleView.isSelected = false + } } - - requestLayout() } - return insets + setPadding(paddingLeft, paddingTop, paddingRight, imeBottomInset) + return insets.inset(0, 0, 0, imeBottomInset) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/IPinPad.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/IPinPad.kt new file mode 100644 index 000000000000..cf6865c0fe8e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/IPinPad.kt @@ -0,0 +1,42 @@ +/* + * 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.biometrics.ui + +/** + * Interface for PinPad in auth_credential_pin_view. This is needed when a custom pin pad is + * preferred to the IME To use a PinPad, one needs to implement IPinPad interface and provide it in + * auth_credential_pin_view and specify the id as [pin_pad] + */ +interface IPinPad { + fun setPinPadClickListener(pinPadClickListener: PinPadClickListener) +} + +/** The call back interface for onClick event in the view. */ +interface PinPadClickListener { + /** + * One of the digit key has been clicked. + * + * @param digit A String representing a digit between 0 and 9. + */ + fun onDigitKeyClick(digit: String?) + + /** The backspace key has been clicked. */ + fun onBackspaceClick() + + /** The enter key has been clicked. */ + fun onEnterKeyClick() +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt index c27d71522c2f..996b62e084cb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt @@ -15,6 +15,7 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.R import com.android.systemui.biometrics.ui.CredentialPasswordView import com.android.systemui.biometrics.ui.CredentialView +import com.android.systemui.biometrics.ui.IPinPad import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.awaitCancellation @@ -53,13 +54,19 @@ object CredentialPasswordViewBinder { } ) passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback)) - + val pinPadView = view.findViewById(R.id.pin_pad) as? IPinPad + if (pinPadView != null) { + PinPadViewBinder.bind(pinPadView, view) + } repeatOnLifecycle(Lifecycle.State.STARTED) { // dismiss on a valid credential check launch { viewModel.validatedAttestation.collect { attestation -> if (attestation != null) { - imeManager.hideSoftInputFromWindow(view.windowToken, 0 /* flags */) + imeManager.hideSoftInputFromWindow( + view.windowToken, + 0 // flag + ) host.onCredentialMatched(attestation) } else { passwordField.setText("") diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt index 4ac9f967920f..25fe61916644 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt @@ -47,6 +47,7 @@ object CredentialViewBinder { val descriptionView: TextView = view.requireViewById(R.id.description) val iconView: ImageView? = view.findViewById(R.id.icon) val errorView: TextView = view.requireViewById(R.id.error) + val cancelButton: Button? = view.findViewById(R.id.cancel_button) val emergencyButtonView: Button = view.requireViewById(R.id.emergencyCallButton) var errorTimer: Job? = null @@ -60,7 +61,7 @@ object CredentialViewBinder { updateForContentDimensions( containerWidth, containerHeight, - 0 /* animateDurationMs */ + 0 // animateDurationMs ) } } @@ -103,7 +104,18 @@ object CredentialViewBinder { } } } - .collect { errorView.textOrHide = it } + .collect { it -> + val hasError = !it.isNullOrBlank() + errorView.visibility = + if (hasError) { + View.VISIBLE + } else if (cancelButton != null) { + View.INVISIBLE + } else { + View.GONE + } + errorView.text = if (hasError) it else "" + } } // show an extra dialog if the remaining attempts becomes low @@ -117,6 +129,8 @@ object CredentialViewBinder { } } + cancelButton?.setOnClickListener { host.onCredentialAborted() } + // bind the auth widget when (view) { is CredentialPasswordView -> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PinPadViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PinPadViewBinder.kt new file mode 100644 index 000000000000..906206c27455 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PinPadViewBinder.kt @@ -0,0 +1,64 @@ +/* + * 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.biometrics.ui.binder + +import android.view.KeyEvent +import android.widget.ImeAwareEditText +import com.android.internal.widget.LockscreenCredential +import com.android.systemui.R +import com.android.systemui.biometrics.ui.CredentialPasswordView +import com.android.systemui.biometrics.ui.IPinPad +import com.android.systemui.biometrics.ui.PinPadClickListener + +/** Binder for IPinPad */ +object PinPadViewBinder { + /** Implements a PinPadClickListener inside a pin pad */ + @JvmStatic + fun bind(view: IPinPad, credentialPasswordView: CredentialPasswordView) { + val passwordField: ImeAwareEditText = + credentialPasswordView.requireViewById(R.id.lockPassword) + view.setPinPadClickListener( + object : PinPadClickListener { + + override fun onDigitKeyClick(digit: String?) { + passwordField.append(digit) + } + + override fun onBackspaceClick() { + val pin = LockscreenCredential.createPinOrNone(passwordField.text) + if (pin.size() > 0) { + passwordField.text.delete( + passwordField.selectionEnd - 1, + passwordField.selectionEnd + ) + } + pin.zeroize() + } + + override fun onEnterKeyClick() { + passwordField.dispatchKeyEvent( + KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0) + ) + passwordField.dispatchKeyEvent( + KeyEvent(0, 0, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0) + ) + } + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 4dc7720ef447..e8b8f54220aa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -289,7 +289,7 @@ constructor( if (authenticateAfterError) { showAuthenticating(messageAfterError) } else { - showHelp(messageAfterError) + showInfo(messageAfterError) } } } @@ -309,12 +309,15 @@ constructor( private fun supportsRetry(failedModality: BiometricModality) = failedModality == BiometricModality.Face + suspend fun showHelp(message: String) = showHelp(message, clearIconError = false) + suspend fun showInfo(message: String) = showHelp(message, clearIconError = true) + /** * Show a persistent help message. * * Will be show even if the user has already authenticated. */ - suspend fun showHelp(message: String) { + private suspend fun showHelp(message: String, clearIconError: Boolean) { val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated if (!alreadyAuthenticated) { _isAuthenticating.value = false @@ -329,6 +332,8 @@ constructor( AuthBiometricView.STATE_PENDING_CONFIRMATION } else if (alreadyAuthenticated && !isConfirmationRequired.first()) { AuthBiometricView.STATE_AUTHENTICATED + } else if (clearIconError) { + AuthBiometricView.STATE_IDLE } else { AuthBiometricView.STATE_HELP } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 484be9ce1975..1b2a9ebc9ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -49,6 +49,7 @@ import com.android.systemui.recents.Recents import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.ImmersiveModeConfirmation +import com.android.systemui.statusbar.gesture.GesturePointerEventListener import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController import com.android.systemui.statusbar.phone.LockscreenWallpaper @@ -182,6 +183,12 @@ abstract class SystemUICoreStartableModule { @ClassKey(ScreenDecorations::class) abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable + /** Inject into GesturePointerEventHandler. */ + @Binds + @IntoMap + @ClassKey(GesturePointerEventListener::class) + abstract fun bindGesturePointerEventListener(sysui: GesturePointerEventListener): CoreStartable + /** Inject into SessionTracker. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 040ee7938f1d..3eb17400446c 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -128,11 +128,11 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.RingerModeTracker; @@ -141,7 +141,6 @@ import com.android.systemui.util.settings.SecureSettings; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -244,6 +243,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final IStatusBarService mStatusBarService; protected final LightBarController mLightBarController; protected final NotificationShadeWindowController mNotificationShadeWindowController; + private final StatusBarWindowController mStatusBarWindowController; private final IWindowManager mIWindowManager; private final Executor mBackgroundExecutor; private final RingerModeTracker mRingerModeTracker; @@ -251,7 +251,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected Handler mMainHandler; private int mSmallestScreenWidthDp; private int mOrientation; - private final Optional<CentralSurfaces> mCentralSurfacesOptional; private final ShadeController mShadeController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final DialogLaunchAnimator mDialogLaunchAnimator; @@ -356,13 +355,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene IStatusBarService statusBarService, LightBarController lightBarController, NotificationShadeWindowController notificationShadeWindowController, + StatusBarWindowController statusBarWindowController, IWindowManager iWindowManager, @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, RingerModeTracker ringerModeTracker, @Main Handler handler, PackageManager packageManager, - Optional<CentralSurfaces> centralSurfacesOptional, ShadeController shadeController, KeyguardUpdateMonitor keyguardUpdateMonitor, DialogLaunchAnimator dialogLaunchAnimator) { @@ -390,13 +389,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mStatusBarService = statusBarService; mLightBarController = lightBarController; mNotificationShadeWindowController = notificationShadeWindowController; + mStatusBarWindowController = statusBarWindowController; mIWindowManager = iWindowManager; mBackgroundExecutor = backgroundExecutor; mRingerModeTracker = ringerModeTracker; mMainHandler = handler; mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; mOrientation = resources.getConfiguration().orientation; - mCentralSurfacesOptional = centralSurfacesOptional; mShadeController = shadeController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mDialogLaunchAnimator = dialogLaunchAnimator; @@ -449,10 +448,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return mUiEventLogger; } - protected Optional<CentralSurfaces> getCentralSurfaces() { - return mCentralSurfacesOptional; - } - protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() { return mKeyguardUpdateMonitor; } @@ -701,12 +696,21 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected ActionsDialogLite createDialog() { initDialogItems(); - ActionsDialogLite dialog = new ActionsDialogLite(mContext, + ActionsDialogLite dialog = new ActionsDialogLite( + mContext, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite, - mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService, + mAdapter, + mOverflowAdapter, + mSysuiColorExtractor, + mStatusBarService, mLightBarController, - mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing, - mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional, + mKeyguardStateController, + mNotificationShadeWindowController, + mStatusBarWindowController, + this::onRefresh, + mKeyguardShowing, + mPowerAdapter, + mUiEventLogger, mShadeController, mKeyguardUpdateMonitor, mLockPatternUtils); @@ -2208,13 +2212,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private boolean mKeyguardShowing; protected float mScrimAlpha; protected final LightBarController mLightBarController; + private final KeyguardStateController mKeyguardStateController; protected final NotificationShadeWindowController mNotificationShadeWindowController; + private final StatusBarWindowController mStatusBarWindowController; private ListPopupWindow mOverflowPopup; private Dialog mPowerOptionsDialog; protected final Runnable mOnRefreshCallback; private UiEventLogger mUiEventLogger; private GestureDetector mGestureDetector; - private Optional<CentralSurfaces> mCentralSurfacesOptional; private final ShadeController mShadeController; private KeyguardUpdateMonitor mKeyguardUpdateMonitor; private LockPatternUtils mLockPatternUtils; @@ -2248,8 +2253,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (distanceY < 0 && distanceY > distanceX - && e1.getY() <= mCentralSurfacesOptional.map( - CentralSurfaces::getStatusBarHeight).orElse(0)) { + && e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) { // Downwards scroll from top openShadeAndDismiss(); return true; @@ -2261,8 +2265,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX) - && e1.getY() <= mCentralSurfacesOptional.map( - CentralSurfaces::getStatusBarHeight).orElse(0)) { + && e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) { // Downwards fling from top openShadeAndDismiss(); return true; @@ -2281,14 +2284,20 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mOverriddenBackDispatcher = mockDispatcher; } - ActionsDialogLite(Context context, int themeRes, MyAdapter adapter, + ActionsDialogLite(Context context, + int themeRes, + MyAdapter adapter, MyOverflowAdapter overflowAdapter, - SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, + SysuiColorExtractor sysuiColorExtractor, + IStatusBarService statusBarService, LightBarController lightBarController, + KeyguardStateController keyguardStateController, NotificationShadeWindowController notificationShadeWindowController, - Runnable onRefreshCallback, boolean keyguardShowing, - MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, - Optional<CentralSurfaces> centralSurfacesOptional, + StatusBarWindowController statusBarWindowController, + Runnable onRefreshCallback, + boolean keyguardShowing, + MyPowerOptionsAdapter powerAdapter, + UiEventLogger uiEventLogger, ShadeController shadeController, KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) { @@ -2302,11 +2311,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mColorExtractor = sysuiColorExtractor; mStatusBarService = statusBarService; mLightBarController = lightBarController; + mKeyguardStateController = keyguardStateController; mNotificationShadeWindowController = notificationShadeWindowController; + mStatusBarWindowController = statusBarWindowController; mOnRefreshCallback = onRefreshCallback; mKeyguardShowing = keyguardShowing; mUiEventLogger = uiEventLogger; - mCentralSurfacesOptional = centralSurfacesOptional; mShadeController = shadeController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; @@ -2355,7 +2365,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private void openShadeAndDismiss() { mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); - if (mCentralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing).orElse(false)) { + if (mKeyguardStateController.isShowing()) { // match existing lockscreen behavior to open QS when swiping from status bar mShadeController.animateExpandQs(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt index e501ece626b4..635961b0ea01 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt @@ -54,11 +54,7 @@ constructor( if (event.handleAction()) { when (event.keyCode) { KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent() - KeyEvent.KEYCODE_SPACE, - KeyEvent.KEYCODE_ENTER -> - if (isDeviceInteractive()) { - return collapseShadeLockedOrShowPrimaryBouncer() - } + KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent() } } return false @@ -94,22 +90,16 @@ constructor( (statusBarStateController.state != StatusBarState.SHADE) && statusBarKeyguardViewManager.shouldDismissOnMenuPressed() if (shouldUnlockOnMenuPressed) { - return collapseShadeLockedOrShowPrimaryBouncer() + shadeController.animateCollapseShadeForced() + return true } return false } - private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean { - when (statusBarStateController.state) { - StatusBarState.SHADE -> return false - StatusBarState.SHADE_LOCKED -> { - shadeController.animateCollapseShadeForced() - return true - } - StatusBarState.KEYGUARD -> { - statusBarKeyguardViewManager.showPrimaryBouncer(true) - return true - } + private fun dispatchSpaceEvent(): Boolean { + if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) { + shadeController.animateCollapseShadeForced() + return true } return false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt new file mode 100644 index 000000000000..c2d2725f4f06 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/qualifiers/KeyguardRootView.kt @@ -0,0 +1,24 @@ +/* + * Copyright 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.keyguard.qualifiers + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyguardRootView diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt index a5b00e09131a..c8dab3223d33 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt @@ -87,7 +87,7 @@ constructor( } addListener( object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { + override fun onAnimationEnd(animation: Animator) { updateIsAnimatingSurface() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt new file mode 100644 index 000000000000..c88737e6bd70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/LockscreenSceneModule.kt @@ -0,0 +1,41 @@ +/* + * Copyright 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.view + +import android.view.View +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.KeyguardViewConfigurator +import com.android.systemui.keyguard.qualifiers.KeyguardRootView +import dagger.Module +import dagger.Provides +import javax.inject.Provider +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@Module +object LockscreenSceneModule { + + @Provides + @SysUISingleton + @KeyguardRootView + fun viewProvider( + configurator: Provider<KeyguardViewConfigurator>, + ): () -> View { + return { configurator.get().getKeyguardRootView() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 6d3b7f18e974..93c4902332c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -16,41 +16,22 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.R import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.common.shared.model.ContentDescription -import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user input for the lockscreen scene. */ @SysUISingleton class LockscreenSceneViewModel @Inject constructor( - @Application applicationScope: CoroutineScope, authenticationInteractor: AuthenticationInteractor, private val bouncerInteractor: BouncerInteractor, ) { - /** The icon for the "lock" button on the lockscreen. */ - val lockButtonIcon: StateFlow<Icon> = - authenticationInteractor.isUnlocked - .map { isUnlocked -> lockIcon(isUnlocked = isUnlocked) } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = lockIcon(isUnlocked = authenticationInteractor.isUnlocked.value), - ) - /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: Flow<SceneKey> = authenticationInteractor.isUnlocked.map { isUnlocked -> @@ -65,31 +46,4 @@ constructor( fun onLockButtonClicked() { bouncerInteractor.showOrUnlockDevice() } - - /** Notifies that some content on the lock screen was clicked. */ - fun onContentClicked() { - bouncerInteractor.showOrUnlockDevice() - } - - private fun upDestinationSceneKey( - canSwipeToDismiss: Boolean, - ): SceneKey { - return if (canSwipeToDismiss) SceneKey.Gone else SceneKey.Bouncer - } - - private fun lockIcon( - isUnlocked: Boolean, - ): Icon { - return if (isUnlocked) { - Icon.Resource( - R.drawable.ic_device_lock_off, - ContentDescription.Resource(R.string.accessibility_unlock_button) - ) - } else { - Icon.Resource( - R.drawable.ic_device_lock_on, - ContentDescription.Resource(R.string.accessibility_lock_icon) - ) - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index 72352e397806..cbd9a34e9a66 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -54,6 +54,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.screenrecord.MediaProjectionPermissionDialog; import com.android.systemui.screenrecord.ScreenShareOption; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -71,6 +72,7 @@ public class MediaProjectionPermissionActivity extends Activity private final FeatureFlags mFeatureFlags; private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver; + private final ActivityStarter mActivityStarter; private String mPackageName; private int mUid; @@ -86,8 +88,10 @@ public class MediaProjectionPermissionActivity extends Activity @Inject public MediaProjectionPermissionActivity(FeatureFlags featureFlags, - Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) { + Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, + ActivityStarter activityStarter) { mFeatureFlags = featureFlags; + mActivityStarter = activityStarter; mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver; } @@ -306,8 +310,16 @@ public class MediaProjectionPermissionActivity extends Activity // Start activity from the current foreground user to avoid creating a separate // SystemUI process without access to recent tasks because it won't have // WM Shell running inside. + // It is also important to make sure the shade is dismissed, otherwise users won't + // see the app selector. mUserSelectingTask = true; - startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser())); + mActivityStarter.startActivity( + intent, + /* dismissShade= */ true, + /* animationController= */ null, + /* showOverLockscreenWhenLocked= */ false, + UserHandle.of(ActivityManager.getCurrentUser()) + ); } } catch (RemoteException e) { Log.e(TAG, "Error granting projection permission", e); diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 9f45f66d9971..1e82d44e413e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -87,7 +87,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBar; @@ -571,7 +570,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis SysUiState sysUiState, Provider<SceneInteractor> sceneInteractor, UserTracker userTracker, - ScreenLifecycle screenLifecycle, WakefulnessLifecycle wakefulnessLifecycle, UiEventLogger uiEventLogger, DisplayTracker displayTracker, @@ -651,7 +649,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Listen for user setup mUserTracker.addCallback(mUserChangedCallback, mMainExecutor); - screenLifecycle.addObserver(mScreenLifecycleObserver); wakefulnessLifecycle.addObserver(mWakefulnessLifecycleObserver); // Connect to the service updateEnabledAndBinding(); @@ -923,60 +920,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - private final ScreenLifecycle.Observer mScreenLifecycleObserver = - new ScreenLifecycle.Observer() { - /** - * Notifies the Launcher that screen turned on and ready to use - */ - @Override - public void onScreenTurnedOn() { - try { - if (mOverviewProxy != null) { - mOverviewProxy.onScreenTurnedOn(); - } else { - Log.e(TAG_OPS, - "Failed to get overview proxy for screen turned on event."); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e); - } - } - - /** - * Notifies the Launcher that screen is starting to turn on. - */ - @Override - public void onScreenTurningOff() { - try { - if (mOverviewProxy != null) { - mOverviewProxy.onScreenTurningOff(); - } else { - Log.e(TAG_OPS, - "Failed to get overview proxy for screen turning off event."); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e); - } - } - - /** - * Notifies the Launcher that screen is starting to turn on. - */ - @Override - public void onScreenTurningOn() { - try { - if (mOverviewProxy != null) { - mOverviewProxy.onScreenTurningOn(); - } else { - Log.e(TAG_OPS, - "Failed to get overview proxy for screen turning on event."); - } - } catch (RemoteException e) { - Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e); - } - } - }; - private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver = new WakefulnessLifecycle.Observer() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 398e64b1981b..714795109454 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.scene +import com.android.systemui.keyguard.ui.view.LockscreenSceneModule import com.android.systemui.scene.domain.startable.SceneContainerStartableModule import com.android.systemui.scene.shared.model.SceneContainerConfigModule import com.android.systemui.scene.ui.composable.SceneModule @@ -24,6 +25,7 @@ import dagger.Module @Module( includes = [ + LockscreenSceneModule::class, SceneContainerConfigModule::class, SceneContainerStartableModule::class, SceneModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt new file mode 100644 index 000000000000..b34c3ac5af41 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt @@ -0,0 +1,46 @@ +/* + * 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.gesture + +import android.content.Context +import android.view.InputEvent +import android.view.MotionEvent +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.settings.DisplayTracker +import javax.inject.Inject + +/** + * A class to detect when a motion event happens. To be notified when the event is detected, add a + * callback via [addOnGestureDetectedCallback]. + */ +@SysUISingleton +class GesturePointerEventDetector @Inject constructor( + private val context: Context, + displayTracker: DisplayTracker +) : GenericGestureDetector( + GesturePointerEventDetector::class.simpleName!!, + displayTracker.defaultDisplayId +) { + override fun onInputEvent(ev: InputEvent) { + if (ev !is MotionEvent) { + return + } + // Pass all events to [gestureDetector], which will then notify [gestureListener] when a tap + // is detected. + onGestureDetected(ev) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt new file mode 100644 index 000000000000..8505c5ff32e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventListener.kt @@ -0,0 +1,504 @@ +/* + * 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.gesture + +import android.content.Context +import android.graphics.Rect +import android.graphics.Region +import android.hardware.display.DisplayManagerGlobal +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.util.Log +import android.view.DisplayCutout +import android.view.DisplayInfo +import android.view.GestureDetector +import android.view.InputDevice +import android.view.InputEvent +import android.view.MotionEvent +import android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT +import android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE +import android.view.ViewRootImpl.CLIENT_TRANSIENT +import android.widget.OverScroller +import com.android.internal.R +import com.android.systemui.CoreStartable +import java.io.PrintWriter +import javax.inject.Inject + +/** + * Watches for gesture events that may trigger system bar related events and notify the registered + * callbacks. Add callback to this listener by calling {@link setCallbacks}. + */ +class GesturePointerEventListener +@Inject +constructor(context: Context, gestureDetector: GesturePointerEventDetector) : CoreStartable { + private val mContext: Context + private val mHandler = Handler(Looper.getMainLooper()) + private var mGestureDetector: GesturePointerEventDetector + private var mFlingGestureDetector: GestureDetector? = null + private var mDisplayCutoutTouchableRegionSize = 0 + + // The thresholds for each edge of the display + private val mSwipeStartThreshold = Rect() + private var mSwipeDistanceThreshold = 0 + private var mCallbacks: Callbacks? = null + private val mDownPointerId = IntArray(MAX_TRACKED_POINTERS) + private val mDownX = FloatArray(MAX_TRACKED_POINTERS) + private val mDownY = FloatArray(MAX_TRACKED_POINTERS) + private val mDownTime = LongArray(MAX_TRACKED_POINTERS) + var screenHeight = 0 + var screenWidth = 0 + private var mDownPointers = 0 + private var mSwipeFireable = false + private var mDebugFireable = false + private var mMouseHoveringAtLeft = false + private var mMouseHoveringAtTop = false + private var mMouseHoveringAtRight = false + private var mMouseHoveringAtBottom = false + private var mLastFlingTime: Long = 0 + + init { + mContext = checkNull("context", context) + mGestureDetector = checkNull("gesture detector", gestureDetector) + onConfigurationChanged() + } + + override fun start() { + if (!CLIENT_TRANSIENT) { + return + } + mGestureDetector.addOnGestureDetectedCallback(TAG) { ev -> onInputEvent(ev) } + mGestureDetector.startGestureListening() + + mFlingGestureDetector = + object : GestureDetector(mContext, FlingGestureDetector(), mHandler) {} + } + + fun onDisplayInfoChanged(info: DisplayInfo) { + screenWidth = info.logicalWidth + screenHeight = info.logicalHeight + onConfigurationChanged() + } + + fun onConfigurationChanged() { + if (!CLIENT_TRANSIENT) { + return + } + val r = mContext.resources + val defaultThreshold = r.getDimensionPixelSize(R.dimen.system_gestures_start_threshold) + mSwipeStartThreshold[defaultThreshold, defaultThreshold, defaultThreshold] = + defaultThreshold + mSwipeDistanceThreshold = defaultThreshold + val display = DisplayManagerGlobal.getInstance().getRealDisplay(mContext.displayId) + val displayCutout = display.cutout + if (displayCutout != null) { + // Expand swipe start threshold such that we can catch touches that just start beyond + // the notch area + mDisplayCutoutTouchableRegionSize = + r.getDimensionPixelSize(R.dimen.display_cutout_touchable_region_size) + val bounds = displayCutout.boundingRectsAll + if (bounds[DisplayCutout.BOUNDS_POSITION_LEFT] != null) { + mSwipeStartThreshold.left = + Math.max( + mSwipeStartThreshold.left, + bounds[DisplayCutout.BOUNDS_POSITION_LEFT]!!.width() + + mDisplayCutoutTouchableRegionSize + ) + } + if (bounds[DisplayCutout.BOUNDS_POSITION_TOP] != null) { + mSwipeStartThreshold.top = + Math.max( + mSwipeStartThreshold.top, + bounds[DisplayCutout.BOUNDS_POSITION_TOP]!!.height() + + mDisplayCutoutTouchableRegionSize + ) + } + if (bounds[DisplayCutout.BOUNDS_POSITION_RIGHT] != null) { + mSwipeStartThreshold.right = + Math.max( + mSwipeStartThreshold.right, + bounds[DisplayCutout.BOUNDS_POSITION_RIGHT]!!.width() + + mDisplayCutoutTouchableRegionSize + ) + } + if (bounds[DisplayCutout.BOUNDS_POSITION_BOTTOM] != null) { + mSwipeStartThreshold.bottom = + Math.max( + mSwipeStartThreshold.bottom, + bounds[DisplayCutout.BOUNDS_POSITION_BOTTOM]!!.height() + + mDisplayCutoutTouchableRegionSize + ) + } + } + if (DEBUG) + Log.d( + TAG, + "mSwipeStartThreshold=$mSwipeStartThreshold" + + " mSwipeDistanceThreshold=$mSwipeDistanceThreshold" + ) + } + + fun onInputEvent(ev: InputEvent) { + if (ev !is MotionEvent) { + return + } + if (DEBUG) Log.d(TAG, "Received motion event $ev") + if (ev.isTouchEvent) { + mFlingGestureDetector?.onTouchEvent(ev) + } + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + mSwipeFireable = true + mDebugFireable = true + mDownPointers = 0 + captureDown(ev, 0) + if (mMouseHoveringAtLeft) { + mMouseHoveringAtLeft = false + mCallbacks?.onMouseLeaveFromLeft() + } + if (mMouseHoveringAtTop) { + mMouseHoveringAtTop = false + mCallbacks?.onMouseLeaveFromTop() + } + if (mMouseHoveringAtRight) { + mMouseHoveringAtRight = false + mCallbacks?.onMouseLeaveFromRight() + } + if (mMouseHoveringAtBottom) { + mMouseHoveringAtBottom = false + mCallbacks?.onMouseLeaveFromBottom() + } + mCallbacks?.onDown() + } + MotionEvent.ACTION_POINTER_DOWN -> { + captureDown(ev, ev.actionIndex) + if (mDebugFireable) { + mDebugFireable = ev.pointerCount < 5 + if (!mDebugFireable) { + if (DEBUG) Log.d(TAG, "Firing debug") + mCallbacks?.onDebug() + } + } + } + MotionEvent.ACTION_MOVE -> + if (mSwipeFireable) { + val trackpadSwipe = detectTrackpadThreeFingerSwipe(ev) + mSwipeFireable = trackpadSwipe == TRACKPAD_SWIPE_NONE + if (!mSwipeFireable) { + if (trackpadSwipe == TRACKPAD_SWIPE_FROM_TOP) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromTop from trackpad") + mCallbacks?.onSwipeFromTop() + } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_BOTTOM) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromBottom from trackpad") + mCallbacks?.onSwipeFromBottom() + } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_RIGHT) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromRight from trackpad") + mCallbacks?.onSwipeFromRight() + } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_LEFT) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromLeft from trackpad") + mCallbacks?.onSwipeFromLeft() + } + } else { + val swipe = detectSwipe(ev) + mSwipeFireable = swipe == SWIPE_NONE + if (swipe == SWIPE_FROM_TOP) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromTop") + mCallbacks?.onSwipeFromTop() + } else if (swipe == SWIPE_FROM_BOTTOM) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromBottom") + mCallbacks?.onSwipeFromBottom() + } else if (swipe == SWIPE_FROM_RIGHT) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromRight") + mCallbacks?.onSwipeFromRight() + } else if (swipe == SWIPE_FROM_LEFT) { + if (DEBUG) Log.d(TAG, "Firing onSwipeFromLeft") + mCallbacks?.onSwipeFromLeft() + } + } + } + MotionEvent.ACTION_HOVER_MOVE -> + if (ev.isFromSource(InputDevice.SOURCE_MOUSE)) { + val eventX = ev.x + val eventY = ev.y + if (!mMouseHoveringAtLeft && eventX == 0f) { + mCallbacks?.onMouseHoverAtLeft() + mMouseHoveringAtLeft = true + } else if (mMouseHoveringAtLeft && eventX > 0) { + mCallbacks?.onMouseLeaveFromLeft() + mMouseHoveringAtLeft = false + } + if (!mMouseHoveringAtTop && eventY == 0f) { + mCallbacks?.onMouseHoverAtTop() + mMouseHoveringAtTop = true + } else if (mMouseHoveringAtTop && eventY > 0) { + mCallbacks?.onMouseLeaveFromTop() + mMouseHoveringAtTop = false + } + if (!mMouseHoveringAtRight && eventX >= screenWidth - 1) { + mCallbacks?.onMouseHoverAtRight() + mMouseHoveringAtRight = true + } else if (mMouseHoveringAtRight && eventX < screenWidth - 1) { + mCallbacks?.onMouseLeaveFromRight() + mMouseHoveringAtRight = false + } + if (!mMouseHoveringAtBottom && eventY >= screenHeight - 1) { + mCallbacks?.onMouseHoverAtBottom() + mMouseHoveringAtBottom = true + } else if (mMouseHoveringAtBottom && eventY < screenHeight - 1) { + mCallbacks?.onMouseLeaveFromBottom() + mMouseHoveringAtBottom = false + } + } + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> { + mSwipeFireable = false + mDebugFireable = false + mCallbacks?.onUpOrCancel() + } + else -> if (DEBUG) Log.d(TAG, "Ignoring $ev") + } + } + + fun setCallbacks(callbacks: Callbacks) { + mCallbacks = callbacks + } + + private fun captureDown(event: MotionEvent, pointerIndex: Int) { + val pointerId = event.getPointerId(pointerIndex) + val i = findIndex(pointerId) + if (DEBUG) Log.d(TAG, "pointer $pointerId down pointerIndex=$pointerIndex trackingIndex=$i") + if (i != UNTRACKED_POINTER) { + mDownX[i] = event.getX(pointerIndex) + mDownY[i] = event.getY(pointerIndex) + mDownTime[i] = event.eventTime + if (DEBUG) + Log.d(TAG, "pointer " + pointerId + " down x=" + mDownX[i] + " y=" + mDownY[i]) + } + } + + protected fun currentGestureStartedInRegion(r: Region): Boolean { + return r.contains(mDownX[0].toInt(), mDownY[0].toInt()) + } + + private fun findIndex(pointerId: Int): Int { + for (i in 0 until mDownPointers) { + if (mDownPointerId[i] == pointerId) { + return i + } + } + if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) { + return UNTRACKED_POINTER + } + mDownPointerId[mDownPointers++] = pointerId + return mDownPointers - 1 + } + + private fun detectTrackpadThreeFingerSwipe(move: MotionEvent): Int { + if (!isTrackpadThreeFingerSwipe(move)) { + return TRACKPAD_SWIPE_NONE + } + val dx = move.x - mDownX[0] + val dy = move.y - mDownY[0] + if (Math.abs(dx) < Math.abs(dy)) { + if (Math.abs(dy) > mSwipeDistanceThreshold) { + return if (dy > 0) TRACKPAD_SWIPE_FROM_TOP else TRACKPAD_SWIPE_FROM_BOTTOM + } + } else { + if (Math.abs(dx) > mSwipeDistanceThreshold) { + return if (dx > 0) TRACKPAD_SWIPE_FROM_LEFT else TRACKPAD_SWIPE_FROM_RIGHT + } + } + return TRACKPAD_SWIPE_NONE + } + + private fun isTrackpadThreeFingerSwipe(event: MotionEvent): Boolean { + return (event.classification == CLASSIFICATION_MULTI_FINGER_SWIPE && + event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3f) + } + private fun detectSwipe(move: MotionEvent): Int { + val historySize = move.historySize + val pointerCount = move.pointerCount + for (p in 0 until pointerCount) { + val pointerId = move.getPointerId(p) + val i = findIndex(pointerId) + if (i != UNTRACKED_POINTER) { + for (h in 0 until historySize) { + val time = move.getHistoricalEventTime(h) + val x = move.getHistoricalX(p, h) + val y = move.getHistoricalY(p, h) + val swipe = detectSwipe(i, time, x, y) + if (swipe != SWIPE_NONE) { + return swipe + } + } + val swipe = detectSwipe(i, move.eventTime, move.getX(p), move.getY(p)) + if (swipe != SWIPE_NONE) { + return swipe + } + } + } + return SWIPE_NONE + } + + private fun detectSwipe(i: Int, time: Long, x: Float, y: Float): Int { + val fromX = mDownX[i] + val fromY = mDownY[i] + val elapsed = time - mDownTime[i] + if (DEBUG) + Log.d( + TAG, + "pointer " + + mDownPointerId[i] + + " moved (" + + fromX + + "->" + + x + + "," + + fromY + + "->" + + y + + ") in " + + elapsed + ) + if ( + fromY <= mSwipeStartThreshold.top && + y > fromY + mSwipeDistanceThreshold && + elapsed < SWIPE_TIMEOUT_MS + ) { + return SWIPE_FROM_TOP + } + if ( + fromY >= screenHeight - mSwipeStartThreshold.bottom && + y < fromY - mSwipeDistanceThreshold && + elapsed < SWIPE_TIMEOUT_MS + ) { + return SWIPE_FROM_BOTTOM + } + if ( + fromX >= screenWidth - mSwipeStartThreshold.right && + x < fromX - mSwipeDistanceThreshold && + elapsed < SWIPE_TIMEOUT_MS + ) { + return SWIPE_FROM_RIGHT + } + return if ( + fromX <= mSwipeStartThreshold.left && + x > fromX + mSwipeDistanceThreshold && + elapsed < SWIPE_TIMEOUT_MS + ) { + SWIPE_FROM_LEFT + } else SWIPE_NONE + } + + fun dump(pw: PrintWriter, prefix: String) { + val inner = "$prefix " + pw.println(prefix + TAG + ":") + pw.print(inner) + pw.print("mDisplayCutoutTouchableRegionSize=") + pw.println(mDisplayCutoutTouchableRegionSize) + pw.print(inner) + pw.print("mSwipeStartThreshold=") + pw.println(mSwipeStartThreshold) + pw.print(inner) + pw.print("mSwipeDistanceThreshold=") + pw.println(mSwipeDistanceThreshold) + } + + private inner class FlingGestureDetector internal constructor() : + GestureDetector.SimpleOnGestureListener() { + private val mOverscroller: OverScroller = OverScroller(mContext) + + override fun onSingleTapUp(e: MotionEvent): Boolean { + if (!mOverscroller.isFinished) { + mOverscroller.forceFinished(true) + } + return true + } + + override fun onFling( + down: MotionEvent?, + up: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + mOverscroller.computeScrollOffset() + val now = SystemClock.uptimeMillis() + if (mLastFlingTime != 0L && now > mLastFlingTime + MAX_FLING_TIME_MILLIS) { + mOverscroller.forceFinished(true) + } + mOverscroller.fling( + 0, + 0, + velocityX.toInt(), + velocityY.toInt(), + Int.MIN_VALUE, + Int.MAX_VALUE, + Int.MIN_VALUE, + Int.MAX_VALUE + ) + var duration = mOverscroller.duration + if (duration > MAX_FLING_TIME_MILLIS) { + duration = MAX_FLING_TIME_MILLIS + } + mLastFlingTime = now + mCallbacks?.onFling(duration) + return true + } + } + + interface Callbacks { + fun onSwipeFromTop() + fun onSwipeFromBottom() + fun onSwipeFromRight() + fun onSwipeFromLeft() + fun onFling(durationMs: Int) + fun onDown() + fun onUpOrCancel() + fun onMouseHoverAtLeft() + fun onMouseHoverAtTop() + fun onMouseHoverAtRight() + fun onMouseHoverAtBottom() + fun onMouseLeaveFromLeft() + fun onMouseLeaveFromTop() + fun onMouseLeaveFromRight() + fun onMouseLeaveFromBottom() + fun onDebug() + } + + companion object { + private const val TAG = "GesturePointerEventHandler" + private const val DEBUG = false + private const val SWIPE_TIMEOUT_MS: Long = 500 + private const val MAX_TRACKED_POINTERS = 32 // max per input system + private const val UNTRACKED_POINTER = -1 + private const val MAX_FLING_TIME_MILLIS = 5000 + private const val SWIPE_NONE = 0 + private const val SWIPE_FROM_TOP = 1 + private const val SWIPE_FROM_BOTTOM = 2 + private const val SWIPE_FROM_RIGHT = 3 + private const val SWIPE_FROM_LEFT = 4 + private const val TRACKPAD_SWIPE_NONE = 0 + private const val TRACKPAD_SWIPE_FROM_TOP = 1 + private const val TRACKPAD_SWIPE_FROM_BOTTOM = 2 + private const val TRACKPAD_SWIPE_FROM_RIGHT = 3 + private const val TRACKPAD_SWIPE_FROM_LEFT = 4 + + private fun <T> checkNull(name: String, arg: T?): T { + requireNotNull(arg) { "$name must not be null" } + return arg + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 634611122184..d667b91ea5ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -388,7 +388,10 @@ constructor( }) ssView.setFalsingManager(falsingManager) ssView.setKeyguardBypassEnabled(bypassController.bypassEnabled) - return (ssView as View).apply { addOnAttachStateChangeListener(stateChangeListener) } + return (ssView as View).apply { + setTag(R.id.tag_smartspace_view, Any()) + addOnAttachStateChangeListener(stateChangeListener) + } } private fun connectSession() { @@ -451,12 +454,6 @@ constructor( session?.requestSmartspaceUpdate() } - fun removeViewsFromParent(viewGroup: ViewGroup) { - smartspaceViews.toList().forEach { - viewGroup.removeView(it as View) - } - } - /** * Disconnects the smartspace view from the smartspace service and cleans up any resources. */ @@ -597,3 +594,4 @@ constructor( } } } + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 62a0d138fd05..5c2f9a8d28ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -39,7 +39,10 @@ class StackCoordinator @Inject internal constructor( override fun attach(pipeline: NotifPipeline) { pipeline.addOnAfterRenderListListener(::onAfterRenderList) - groupExpansionManagerImpl.attach(pipeline) + // TODO(b/282865576): This has an issue where it makes changes to some groups without + // notifying listeners. To be fixed in QPR, but for now let's comment it out to avoid the + // group expansion bug. + // groupExpansionManagerImpl.attach(pipeline) } fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java index 5d33804ab6a7..46af03a438f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java @@ -67,29 +67,18 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl * Cleanup entries from mExpandedGroups that no longer exist in the pipeline. */ private final OnBeforeRenderListListener mNotifTracker = (entries) -> { - if (mExpandedGroups.isEmpty()) { - return; // nothing to do - } - final Set<NotificationEntry> renderingSummaries = new HashSet<>(); for (ListEntry entry : entries) { if (entry instanceof GroupEntry) { renderingSummaries.add(entry.getRepresentativeEntry()); } } - - // Create a copy of mExpandedGroups so we can modify it in a thread-safe way. - final var currentExpandedGroups = new HashSet<>(mExpandedGroups); - for (NotificationEntry entry : currentExpandedGroups) { - setExpanded(entry, renderingSummaries.contains(entry)); - } + mExpandedGroups.removeIf(expandedGroup -> !renderingSummaries.contains(expandedGroup)); }; public void attach(NotifPipeline pipeline) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - mDumpManager.registerDumpable(this); - pipeline.addOnBeforeRenderListListener(mNotifTracker); - } + mDumpManager.registerDumpable(this); + pipeline.addOnBeforeRenderListListener(mNotifTracker); } @Override @@ -105,24 +94,11 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl @Override public void setGroupExpanded(NotificationEntry entry, boolean expanded) { final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry); - setExpanded(groupSummary, expanded); - } - - /** - * Add or remove {@code entry} to/from {@code mExpandedGroups} and notify listeners if - * something changed. This assumes that {@code entry} is a group summary. - * <p> - * TODO(b/293434635): Currently, in spite of its docs, - * {@code mGroupMembershipManager.getGroupSummary(entry)} returns null if {@code entry} is - * already a summary. Instead of needing this helper method to bypass that, we probably want to - * move this code back to {@code setGroupExpanded} and use that everywhere. - */ - private void setExpanded(NotificationEntry entry, boolean expanded) { boolean changed; if (expanded) { - changed = mExpandedGroups.add(entry); + changed = mExpandedGroups.add(groupSummary); } else { - changed = mExpandedGroups.remove(entry); + changed = mExpandedGroups.remove(groupSummary); } // Only notify listeners if something changed. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt index 4429939a515c..d10b556de0fb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt @@ -21,29 +21,25 @@ import android.util.AttributeSet import android.util.Log import android.view.LayoutInflater import android.view.View -import com.android.systemui.Dumpable -import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES -import com.android.systemui.util.asIndenting -import com.android.systemui.util.withIncreasedIndent -import java.io.PrintWriter -import javax.inject.Inject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import javax.inject.Named /** * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of * [NotifRemoteViewsFactory] objects to create replacement views for Notification RemoteViews. */ -open class NotifLayoutInflaterFactory -@Inject +class NotifLayoutInflaterFactory +@AssistedInject constructor( - dumpManager: DumpManager, + @Assisted private val row: ExpandableNotificationRow, + @Assisted @InflationFlag val layoutType: Int, @Named(NOTIF_REMOTEVIEWS_FACTORIES) private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory> -) : LayoutInflater.Factory2, Dumpable { - init { - dumpManager.registerNormalDumpable(TAG, this) - } +) : LayoutInflater.Factory2 { override fun onCreateView( parent: View?, @@ -51,41 +47,32 @@ constructor( context: Context, attrs: AttributeSet ): View? { - var view: View? = null var handledFactory: NotifRemoteViewsFactory? = null + var result: View? = null for (layoutFactory in remoteViewsFactories) { - view = layoutFactory.instantiate(parent, name, context, attrs) - if (view != null) { + layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run { check(handledFactory == null) { - "${layoutFactory.javaClass.name} tries to produce view. However, " + - "${handledFactory?.javaClass?.name} produced view for $name before." + "$layoutFactory tries to produce name:$name with type:$layoutType. " + + "However, $handledFactory produced view for $name before." } handledFactory = layoutFactory + result = this } } - logOnCreateView(name, view, handledFactory) - return view + logOnCreateView(name, result, handledFactory) + return result } override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? = onCreateView(null, name, context, attrs) - override fun dump(pw: PrintWriter, args: Array<out String>) { - val indentingPW = pw.asIndenting() - - indentingPW.appendLine("$TAG ReplacementFactories:") - indentingPW.withIncreasedIndent { - remoteViewsFactories.forEach { indentingPW.appendLine(it.javaClass.simpleName) } - } - } - private fun logOnCreateView( name: String, replacedView: View?, factory: NotifRemoteViewsFactory? ) { if (SPEW && replacedView != null && factory != null) { - Log.d(TAG, "$factory produced view for $name: $replacedView") + Log.d(TAG, "$factory produced $replacedView for name:$name with type:$layoutType") } } @@ -93,4 +80,12 @@ constructor( private const val TAG = "NotifLayoutInflaterFac" private val SPEW = Log.isLoggable(TAG, Log.VERBOSE) } + + @AssistedFactory + interface Provider { + fun provide( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int + ): NotifLayoutInflaterFactory + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt index eebd4d4e955f..bf740e94f81c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt @@ -19,10 +19,18 @@ package com.android.systemui.statusbar.notification.row import android.content.Context import android.util.AttributeSet import android.view.View +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag /** Interface used to create replacement view instances in Notification RemoteViews. */ interface NotifRemoteViewsFactory { /** return the replacement view instance for the given view name */ - fun instantiate(parent: View?, name: String, context: Context, attrs: AttributeSet): View? + fun instantiate( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int, + parent: View?, + name: String, + context: Context, + attrs: AttributeSet + ): View? } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 0ad77bdd866b..86f545dc190e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -79,7 +79,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final ConversationNotificationProcessor mConversationProcessor; private final Executor mBgExecutor; private final SmartReplyStateInflater mSmartReplyStateInflater; - private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory; + private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; @Inject NotificationContentInflater( @@ -89,14 +89,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder MediaFeatureFlag mediaFeatureFlag, @Background Executor bgExecutor, SmartReplyStateInflater smartRepliesInflater, - NotifLayoutInflaterFactory notifLayoutInflaterFactory) { + NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { mRemoteViewCache = remoteViewCache; mRemoteInputManager = remoteInputManager; mConversationProcessor = conversationProcessor; mIsMediaInQS = mediaFeatureFlag.getEnabled(); mBgExecutor = bgExecutor; mSmartReplyStateInflater = smartRepliesInflater; - mNotifLayoutInflaterFactory = notifLayoutInflaterFactory; + mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider; } @Override @@ -141,7 +141,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRemoteInputManager.getRemoteViewsOnClickHandler(), mIsMediaInQS, mSmartReplyStateInflater, - mNotifLayoutInflaterFactory); + mNotifLayoutInflaterFactoryProvider); if (mInflateSynchronously) { task.onPostExecute(task.doInBackground()); } else { @@ -165,7 +165,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder bindParams.usesIncreasedHeight, bindParams.usesIncreasedHeadsUpHeight, packageContext, - mNotifLayoutInflaterFactory); + row, + mNotifLayoutInflaterFactoryProvider); result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(), packageContext, @@ -304,7 +305,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags, Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, Context packageContext, - NotifLayoutInflaterFactory notifLayoutInflaterFactory) { + ExpandableNotificationRow row, + NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { InflationProgress result = new InflationProgress(); if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { @@ -322,7 +324,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { result.newPublicView = builder.makePublicContentView(isLowPriority); } - setNotifsViewsInflaterFactory(result, notifLayoutInflaterFactory); + setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider); result.packageContext = packageContext; result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */); result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText( @@ -331,12 +333,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder } private static void setNotifsViewsInflaterFactory(InflationProgress result, - NotifLayoutInflaterFactory notifLayoutInflaterFactory) { - setRemoteViewsInflaterFactory(result.newContentView, notifLayoutInflaterFactory); + ExpandableNotificationRow row, + NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { + setRemoteViewsInflaterFactory(result.newContentView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED)); setRemoteViewsInflaterFactory(result.newExpandedView, - notifLayoutInflaterFactory); - setRemoteViewsInflaterFactory(result.newHeadsUpView, notifLayoutInflaterFactory); - setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactory); + notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_EXPANDED)); + setRemoteViewsInflaterFactory(result.newHeadsUpView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP)); + setRemoteViewsInflaterFactory(result.newPublicView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_PUBLIC)); } private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews, @@ -821,7 +827,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final ConversationNotificationProcessor mConversationProcessor; private final boolean mIsMediaInQS; private final SmartReplyStateInflater mSmartRepliesInflater; - private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory; + private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; private AsyncInflationTask( Executor bgExecutor, @@ -838,7 +844,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder RemoteViews.InteractionHandler remoteViewClickHandler, boolean isMediaFlagEnabled, SmartReplyStateInflater smartRepliesInflater, - NotifLayoutInflaterFactory notifLayoutInflaterFactory) { + NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) { mEntry = entry; mRow = row; mBgExecutor = bgExecutor; @@ -854,7 +860,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mCallback = callback; mConversationProcessor = conversationProcessor; mIsMediaInQS = isMediaFlagEnabled; - mNotifLayoutInflaterFactory = notifLayoutInflaterFactory; + mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider; entry.setInflationTask(this); } @@ -898,8 +904,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder } InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight, - mUsesIncreasedHeadsUpHeight, packageContext, - mNotifLayoutInflaterFactory); + mUsesIncreasedHeadsUpHeight, packageContext, mRow, + mNotifLayoutInflaterFactoryProvider); InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState(); InflationProgress result = inflateSmartReplyViews( inflationProgress, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt index c27682726da5..96547db1283a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt @@ -23,10 +23,13 @@ import android.widget.TextView import com.android.internal.widget.ConversationLayout import com.android.internal.widget.ImageFloatingTextView import com.android.internal.widget.MessagingLayout +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag import javax.inject.Inject class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory { override fun instantiate( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int, parent: View?, name: String, context: Context, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index af09bf281c0c..d0a093cf905a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -196,8 +196,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { /** Get the Keyguard Message Area that displays auth messages. */ AuthKeyguardMessageArea getKeyguardMessageArea(); - int getStatusBarHeight(); - boolean isLaunchingActivityOverLockscreen(); void onKeyguardViewManagerStatesUpdated(); @@ -228,9 +226,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { /** */ boolean getCommandQueuePanelsEnabled(); - /** */ - int getStatusBarWindowState(); - BiometricUnlockController getBiometricUnlockController(); void showWirelessChargingAnimation(int batteryLevel); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index ccb87bf44dc8..a1776c667b94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1688,11 +1688,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return getNotificationShadeWindowViewController().getKeyguardMessageArea(); } - @Override - public int getStatusBarHeight() { - return mStatusBarWindowController.getStatusBarHeight(); - } - private void updateReportRejectedTouchVisibility() { if (mReportRejectedTouch == null) { return; @@ -1818,11 +1813,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public int getStatusBarWindowState() { - return mStatusBarWindowState; - } - - @Override public BiometricUnlockController getBiometricUnlockController() { return mBiometricUnlockController; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 4de669c0b34a..cc41bf843565 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.statusbar.phone -import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR import android.content.res.Configuration import android.graphics.Point @@ -34,6 +33,7 @@ import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.UNFOLD_STATUS_BAR import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider @@ -53,6 +53,7 @@ class PhoneStatusBarViewController private constructor( view: PhoneStatusBarView, @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?, private val centralSurfaces: CentralSurfaces, + private val statusBarWindowStateController: StatusBarWindowStateController, private val shadeController: ShadeController, private val shadeViewController: ShadeViewController, private val windowRootView: Provider<WindowRootView>, @@ -148,7 +149,7 @@ class PhoneStatusBarViewController private constructor( /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ fun onTouch(event: MotionEvent) { - if (centralSurfaces.statusBarWindowState == WINDOW_STATE_SHOWING) { + if (statusBarWindowStateController.windowIsShowing()) { val upOrCancel = event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL @@ -242,6 +243,7 @@ class PhoneStatusBarViewController private constructor( private val featureFlags: FeatureFlags, private val userChipViewModel: StatusBarUserChipViewModel, private val centralSurfaces: CentralSurfaces, + private val statusBarWindowStateController: StatusBarWindowStateController, private val shadeController: ShadeController, private val shadeViewController: ShadeViewController, private val windowRootView: Provider<WindowRootView>, @@ -264,6 +266,7 @@ class PhoneStatusBarViewController private constructor( view, progressProvider.getOrNull(), centralSurfaces, + statusBarWindowStateController, shadeController, shadeViewController, windowRootView, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index ea57eb46bab4..27b8406e3bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -92,6 +92,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.unfold.FoldAodAnimationController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -101,7 +103,6 @@ import java.util.Set; import javax.inject.Inject; -import dagger.Lazy; import kotlinx.coroutines.CoroutineDispatcher; /** @@ -450,14 +451,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } - private KeyguardStateController.Callback mKeyguardStateControllerCallback = - new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide()); - } - }; - private void registerListeners() { mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback); mStatusBarStateController.addCallback(this); @@ -471,7 +464,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mDockManager.addListener(mDockEventListener); mIsDocked = mDockManager.isDocked(); } - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { mShadeViewController.postToView(() -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index cd1afc727346..37f032b464b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -226,7 +226,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( val builder = InteractionJankMonitor.Configuration.Builder .withView( InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD, - notifShadeWindowControllerLazy.get().windowRootView + checkNotNull(notifShadeWindowControllerLazy.get().windowRootView) ) .setTag(statusBarStateControllerImpl.getClockId()) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index 98d4d22d59b4..1be87463250f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -23,7 +23,6 @@ import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLE import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -136,6 +135,10 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); + mFakeDateView.setTag(R.id.tag_smartspace_view, new Object()); + mFakeWeatherView.setTag(R.id.tag_smartspace_view, new Object()); + mFakeSmartspaceView.setTag(R.id.tag_smartspace_view, new Object()); + when(mView.findViewById(R.id.left_aligned_notification_icon_container)) .thenReturn(mNotificationIcons); when(mNotificationIcons.getLayoutParams()).thenReturn( @@ -158,12 +161,6 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView); when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView); when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); - doAnswer(invocation -> { - removeView(mFakeDateView); - removeView(mFakeWeatherView); - removeView(mFakeSmartspaceView); - return null; - }).when(mSmartspaceController).removeViewsFromParent(any()); mExecutor = new FakeExecutor(new FakeSystemClock()); mFakeFeatureFlags = new FakeFeatureFlags(); mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt index da55d5a099b7..95b72d554896 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt @@ -28,7 +28,7 @@ import org.junit.runners.Parameterized.Parameters @SmallTest @RunWith(Parameterized::class) class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() { - val underTest = BoundingBoxOverlapDetector() + val underTest = BoundingBoxOverlapDetector(1f) @Test fun isGoodOverlap() { @@ -83,7 +83,7 @@ private val TOUCH_DATA = GESTURE_START ) -private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */) +private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */) private val OVERLAY = Rect(0 /* left */, 100 /* top */, 400 /* right */, 600 /* bottom */) private fun genTestCases( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 11b0b0798ebc..47084c087952 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -312,10 +312,13 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(message).isEqualTo(PromptMessage.Empty) assertThat(messageVisible).isFalse() } + val clearIconError = !restart assertThat(legacyState) .isEqualTo( if (restart) { AuthBiometricView.STATE_AUTHENTICATING + } else if (clearIconError) { + AuthBiometricView.STATE_IDLE } else { AuthBiometricView.STATE_HELP } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 6aa5a00c36da..b1cf0517ddd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -70,10 +71,10 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; @@ -89,7 +90,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.List; -import java.util.Optional; import java.util.concurrent.Executor; @SmallTest @@ -119,6 +119,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private IStatusBarService mStatusBarService; @Mock private LightBarController mLightBarController; @Mock private NotificationShadeWindowController mNotificationShadeWindowController; + @Mock private StatusBarWindowController mStatusBarWindowController; @Mock private IWindowManager mWindowManager; @Mock private Executor mBackgroundExecutor; @Mock private UiEventLogger mUiEventLogger; @@ -128,7 +129,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private Handler mHandler; @Mock private UserContextProvider mUserContextProvider; @Mock private VibratorHelper mVibratorHelper; - @Mock private CentralSurfaces mCentralSurfaces; @Mock private ShadeController mShadeController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; @@ -172,13 +172,13 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mStatusBarService, mLightBarController, mNotificationShadeWindowController, + mStatusBarWindowController, mWindowManager, mBackgroundExecutor, mUiEventLogger, mRingerModeTracker, mHandler, mPackageManager, - Optional.of(mCentralSurfaces), mShadeController, mKeyguardUpdateMonitor, mDialogLaunchAnimator); @@ -305,7 +305,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); - doReturn(true).when(mCentralSurfaces).isKeyguardShowing(); + doReturn(true).when(mKeyguardStateController).isShowing(); String[] actions = { GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, @@ -329,7 +329,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); - doReturn(false).when(mCentralSurfaces).isKeyguardShowing(); + doReturn(false).when(mKeyguardStateController).isShowing(); String[] actions = { GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, @@ -348,6 +348,34 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { } @Test + public void testSwipeDown_pastStatusBarHeight_shadeNotOpened() { + mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite); + doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); + doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); + doReturn(false).when(mKeyguardStateController).isShowing(); + String[] actions = { + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); + GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); + + doReturn(100).when(mStatusBarWindowController).getStatusBarHeight(); + + GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); + // WHEN the start y is larger than the status bar height + MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 200, 0); + MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0); + gestureListener.onFling(start, end, 0, 1000); + + // THEN the shade isn't opened + verify(mShadeController, never()).animateExpandShade(); + } + + @Test public void testShouldLogBugreportPress() throws InterruptedException { GlobalActionsDialogLite.BugReportAction bugReportAction = mGlobalActionsDialogLite.makeBugReportActionForTesting(); @@ -539,7 +567,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems(); doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); - doReturn(false).when(mCentralSurfaces).isKeyguardShowing(); + doReturn(false).when(mKeyguardStateController).isShowing(); String[] actions = { GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt index e0ae0c359f07..a3f7fc5fc8cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt @@ -131,12 +131,14 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { } @Test - fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_showsPrimaryBouncer() { + fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() { keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test @@ -145,48 +147,42 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_doNothing() { + fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() { keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionsDoNothing(KeyEvent.KEYCODE_MENU) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_showsPrimaryBouncer() { - keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) - whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() { - keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) - - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_enterActionUp_interactiveKeyguard_showsPrimaryBouncer() { + fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() { keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER) - } - - @Test - fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() { - keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode) - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER) + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test @@ -256,42 +252,4 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { .isFalse() verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any()) } - - private fun verifyActionUpCollapsesTheShade(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(shadeController).animateCollapseShadeForced() - } - - private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true)) - } - - private fun verifyActionsDoNothing(keycode: Int) { - // action down: does nothing - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: doesNothing - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 23f243c8ebda..a9f288d3575f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -17,10 +17,8 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest -import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.model.AuthenticationMethodModel -import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -48,7 +46,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val underTest = LockscreenSceneViewModel( - applicationScope = testScope.backgroundScope, authenticationInteractor = authenticationInteractor, bouncerInteractor = utils.bouncerInteractor( @@ -58,32 +55,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { ) @Test - fun lockButtonIcon_whenLocked() = - testScope.runTest { - val lockButtonIcon by collectLastValue(underTest.lockButtonIcon) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - utils.authenticationRepository.setUnlocked(false) - - assertThat((lockButtonIcon as? Icon.Resource)?.res) - .isEqualTo(R.drawable.ic_device_lock_on) - } - - @Test - fun lockButtonIcon_whenUnlocked() = - testScope.runTest { - val lockButtonIcon by collectLastValue(underTest.lockButtonIcon) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - utils.authenticationRepository.setUnlocked(true) - - assertThat((lockButtonIcon as? Icon.Resource)?.res) - .isEqualTo(R.drawable.ic_device_lock_off) - } - - @Test fun upTransitionSceneKey_canSwipeToUnlock_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) @@ -120,32 +91,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { } @Test - fun onContentClicked_deviceUnlocked_switchesToGone() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(true) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) - runCurrent() - - underTest.onContentClicked() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - } - - @Test fun onLockButtonClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index 49ece66e0cfd..ef07fab70bb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -31,7 +31,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.model.SysUiState import com.android.systemui.navigationbar.NavigationBarController @@ -84,7 +83,6 @@ class OverviewProxyServiceTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() private val sysUiState = SysUiState(displayTracker) private val featureFlags = FakeFeatureFlags() - private val screenLifecycle = ScreenLifecycle(dumpManager) private val wakefulnessLifecycle = WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager) @@ -142,7 +140,6 @@ class OverviewProxyServiceTest : SysuiTestCase() { sysUiState, mock(), userTracker, - screenLifecycle, wakefulnessLifecycle, uiEventLogger, displayTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 53c04ccbdb38..46cbfacb0044 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -116,7 +116,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val lockscreenSceneViewModel = LockscreenSceneViewModel( - applicationScope = testScope.backgroundScope, authenticationInteractor = authenticationInteractor, bouncerInteractor = bouncerInteractor, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index ecaf13711b52..48665fe0c9b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -340,9 +340,9 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun knownPluginAttached_clockAndListChanged_notLoaded() { val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.falcon.one") + whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.clocks.metro") val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.falcon.two") + whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.clocks.bignum") var changeCallCount = 0 var listChangeCallCount = 0 diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt index 9393a4f4eead..ee3d87089b6d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt @@ -60,18 +60,4 @@ class RotationButtonControllerTest : SysuiTestCase() { assertThat(mController.canShowRotationButton()).isTrue() } - - @Test - fun ifTaskbarVisible_showRotationSuggestion() { - mController.onNavigationBarWindowVisibilityChange( /* showing = */ false) - mController.onBehaviorChanged(Display.DEFAULT_DISPLAY, - WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) - mController.onNavigationModeChanged(WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON) - mController.onTaskbarStateChange( /* visible = */ false, /* stashed = */ false) - assertThat(mController.canShowRotationButton()).isFalse() - - mController.onTaskbarStateChange( /* visible = */ true, /* stashed = */ false) - - assertThat(mController.canShowRotationButton()).isTrue() - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt index 38a8f414b0fb..4a94dc819a9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt @@ -21,21 +21,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder -import com.android.systemui.statusbar.notification.collection.ListEntry -import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener -import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor import org.junit.Assert import org.junit.Before import org.junit.Test -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest @@ -46,43 +36,13 @@ class GroupExpansionManagerTest : SysuiTestCase() { private val groupMembershipManager: GroupMembershipManager = mock() private val featureFlags = FakeFeatureFlags() - private val pipeline: NotifPipeline = mock() - private lateinit var beforeRenderListListener: OnBeforeRenderListListener - - private val summary1 = notificationEntry("foo", 1) - private val summary2 = notificationEntry("bar", 1) - private val entries = - listOf<ListEntry>( - GroupEntryBuilder() - .setSummary(summary1) - .setChildren( - listOf( - notificationEntry("foo", 2), - notificationEntry("foo", 3), - notificationEntry("foo", 4) - ) - ) - .build(), - GroupEntryBuilder() - .setSummary(summary2) - .setChildren( - listOf( - notificationEntry("bar", 2), - notificationEntry("bar", 3), - notificationEntry("bar", 4) - ) - ) - .build(), - notificationEntry("baz", 1) - ) - - private fun notificationEntry(pkg: String, id: Int) = - NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() } + private val entry1 = NotificationEntryBuilder().build() + private val entry2 = NotificationEntryBuilder().build() @Before fun setUp() { - whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1) - whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2) + whenever(groupMembershipManager.getGroupSummary(entry1)).thenReturn(entry1) + whenever(groupMembershipManager.getGroupSummary(entry2)).thenReturn(entry2) gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags) } @@ -94,15 +54,15 @@ class GroupExpansionManagerTest : SysuiTestCase() { var listenerCalledCount = 0 gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - gem.setGroupExpanded(summary1, false) + gem.setGroupExpanded(entry1, false) Assert.assertEquals(0, listenerCalledCount) - gem.setGroupExpanded(summary1, true) + gem.setGroupExpanded(entry1, true) Assert.assertEquals(1, listenerCalledCount) - gem.setGroupExpanded(summary2, true) + gem.setGroupExpanded(entry2, true) Assert.assertEquals(2, listenerCalledCount) - gem.setGroupExpanded(summary1, true) + gem.setGroupExpanded(entry1, true) Assert.assertEquals(2, listenerCalledCount) - gem.setGroupExpanded(summary2, false) + gem.setGroupExpanded(entry2, false) Assert.assertEquals(3, listenerCalledCount) } @@ -113,39 +73,15 @@ class GroupExpansionManagerTest : SysuiTestCase() { var listenerCalledCount = 0 gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - gem.setGroupExpanded(summary1, false) + gem.setGroupExpanded(entry1, false) Assert.assertEquals(1, listenerCalledCount) - gem.setGroupExpanded(summary1, true) + gem.setGroupExpanded(entry1, true) Assert.assertEquals(2, listenerCalledCount) - gem.setGroupExpanded(summary2, true) + gem.setGroupExpanded(entry2, true) Assert.assertEquals(3, listenerCalledCount) - gem.setGroupExpanded(summary1, true) + gem.setGroupExpanded(entry1, true) Assert.assertEquals(4, listenerCalledCount) - gem.setGroupExpanded(summary2, false) + gem.setGroupExpanded(entry2, false) Assert.assertEquals(5, listenerCalledCount) } - - @Test - fun testSyncWithPipeline() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - gem.attach(pipeline) - beforeRenderListListener = withArgCaptor { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - val listener: OnGroupExpansionChangeListener = mock() - gem.registerGroupExpansionChangeListener(listener) - - beforeRenderListListener.onBeforeRenderList(entries) - verify(listener, never()).onGroupExpansionChange(any(), any()) - - // Expand one of the groups. - gem.setGroupExpanded(summary1, true) - verify(listener).onGroupExpansionChange(summary1.row, true) - - // Empty the pipeline list and verify that the group is no longer expanded. - beforeRenderListListener.onBeforeRenderList(emptyList()) - verify(listener).onGroupExpansionChange(summary1.row, false) - verifyNoMoreInteractions(listener) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt index d5612e8e8007..3f7fc979b1e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt @@ -20,19 +20,21 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.AttributeSet import android.view.View +import android.widget.Button import android.widget.FrameLayout import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager -import junit.framework.Assert.assertEquals -import junit.framework.Assert.assertNotNull -import junit.framework.Assert.assertNull -import org.junit.Before +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify /** Tests for [NotifLayoutInflaterFactory] */ @SmallTest @@ -40,87 +42,106 @@ import org.mockito.MockitoAnnotations @RunWithLooper class NotifLayoutInflaterFactoryTest : SysuiTestCase() { - @Mock private lateinit var attrs: AttributeSet + private lateinit var inflaterFactory: NotifLayoutInflaterFactory - @Before - fun before() { - MockitoAnnotations.initMocks(this) + private val attrs: AttributeSet = mock() + private val row: ExpandableNotificationRow = mock() + private val textViewExpandedFactory = + createReplacementViewFactory("TextView", FLAG_CONTENT_VIEW_EXPANDED) { context, _ -> + Button(context) + } + private val textViewCollapsedFactory = + createReplacementViewFactory("TextView", FLAG_CONTENT_VIEW_CONTRACTED) { context, _ -> + Button(context) + } + private val textViewExpandedFactorySpy = spy(textViewExpandedFactory) + private val textViewCollapsedFactorySpy = spy(textViewCollapsedFactory) + private val viewFactorySpies = setOf(textViewExpandedFactorySpy, textViewCollapsedFactorySpy) + + @Test + fun onCreateView_noMatchingViewForName_returnNull() { + // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts + val layoutType = FLAG_CONTENT_VIEW_EXPANDED + inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) + + // WHEN we try to inflate an ImageView for the expanded layout + val createdView = inflaterFactory.onCreateView("ImageView", context, attrs) + + // THEN the inflater factory returns null + viewFactorySpies.forEach { viewFactory -> + verify(viewFactory).instantiate(row, layoutType, null, "ImageView", context, attrs) + } + assertThat(createdView).isNull() } @Test - fun onCreateView_notMatchingViews_returnNull() { - // GIVEN - val layoutInflaterFactory = - createNotifLayoutInflaterFactoryImpl( - setOf( - createReplacementViewFactory("TextView") { context, attrs -> - FrameLayout(context) - } - ) - ) + fun onCreateView_noMatchingViewForLayoutType_returnNull() { + // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts + val layoutType = FLAG_CONTENT_VIEW_HEADS_UP + inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) - // WHEN - val createView = layoutInflaterFactory.onCreateView("ImageView", mContext, attrs) + // WHEN we try to inflate a TextView for the heads-up layout + val createdView = inflaterFactory.onCreateView("TextView", context, attrs) - // THEN - assertNull(createView) + // THEN the inflater factory returns null + viewFactorySpies.forEach { viewFactory -> + verify(viewFactory).instantiate(row, layoutType, null, "TextView", context, attrs) + } + assertThat(createdView).isNull() } @Test fun onCreateView_matchingViews_returnReplacementView() { - // GIVEN - val layoutInflaterFactory = - createNotifLayoutInflaterFactoryImpl( - setOf( - createReplacementViewFactory("TextView") { context, attrs -> - FrameLayout(context) - } - ) - ) + // GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts + val layoutType = FLAG_CONTENT_VIEW_EXPANDED + inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies) - // WHEN - val createView = layoutInflaterFactory.onCreateView("TextView", mContext, attrs) + // WHEN we try to inflate a TextView for the expanded layout + val createdView = inflaterFactory.onCreateView("TextView", context, attrs) - // THEN - assertNotNull(createView) - assertEquals(requireNotNull(createView)::class.java, FrameLayout::class.java) + // THEN the expanded viewFactory returns the replaced view + verify(textViewCollapsedFactorySpy) + .instantiate(row, layoutType, null, "TextView", context, attrs) + assertThat(createdView).isInstanceOf(Button::class.java) } @Test(expected = IllegalStateException::class) fun onCreateView_multipleFactory_throwIllegalStateException() { - // GIVEN - val layoutInflaterFactory = - createNotifLayoutInflaterFactoryImpl( + // GIVEN we have two factories that replaces TextViews in expanded layouts + val layoutType = FLAG_CONTENT_VIEW_EXPANDED + inflaterFactory = + NotifLayoutInflaterFactory( + row, + layoutType, setOf( - createReplacementViewFactory("TextView") { context, attrs -> + createReplacementViewFactory("TextView", layoutType) { context, _ -> FrameLayout(context) }, - createReplacementViewFactory("TextView") { context, attrs -> + createReplacementViewFactory("TextView", layoutType) { context, _ -> LinearLayout(context) } ) ) - // WHEN - layoutInflaterFactory.onCreateView("TextView", mContext, attrs) + // WHEN we try to inflate a TextView for the expanded layout + inflaterFactory.onCreateView("TextView", mContext, attrs) } - private fun createNotifLayoutInflaterFactoryImpl( - replacementViewFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory> - ) = NotifLayoutInflaterFactory(DumpManager(), replacementViewFactories) - private fun createReplacementViewFactory( replacementName: String, + @InflationFlag replacementLayoutType: Int, createView: (context: Context, attrs: AttributeSet) -> View ) = object : NotifRemoteViewsFactory { override fun instantiate( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int, parent: View?, name: String, context: Context, attrs: AttributeSet ): View? = - if (replacementName == name) { + if (replacementName == name && replacementLayoutType == layoutType) { createView(context, attrs) } else { null diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index f55b0a8ff4da..ea87c808debd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -92,6 +92,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { @Mock private ConversationNotificationProcessor mConversationNotificationProcessor; @Mock private InflatedSmartReplyState mInflatedSmartReplyState; @Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies; + @Mock private NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory; private final SmartReplyStateInflater mSmartReplyStateInflater = @@ -124,6 +125,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase { TestableLooper.get(this)); ExpandableNotificationRow row = helper.createRow(mBuilder.build()); mRow = spy(row); + when(mNotifLayoutInflaterFactoryProvider.provide(any(), any())) + .thenReturn(mNotifLayoutInflaterFactory); mNotificationInflater = new NotificationContentInflater( mCache, @@ -132,7 +135,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mock(MediaFeatureFlag.class), mock(Executor.class), mSmartReplyStateInflater, - mNotifLayoutInflaterFactory); + mNotifLayoutInflaterFactoryProvider); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 1ab2b388de83..3657bdfeed02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -175,7 +175,7 @@ public class NotificationTestHelper { mock(MediaFeatureFlag.class), mock(Executor.class), new MockSmartReplyInflater(), - mock(NotifLayoutInflaterFactory.class)); + mock(NotifLayoutInflaterFactory.Provider.class)); contentBinder.setInflateSynchronously(true); mBindStage = new RowContentBindStage(contentBinder, mock(NotifInflationErrorManager.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index 0b31523e9b98..4c3c3f9d8da5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -16,6 +16,10 @@ package com.android.systemui.statusbar.phone +import android.app.StatusBarManager.WINDOW_STATE_HIDDEN +import android.app.StatusBarManager.WINDOW_STATE_HIDING +import android.app.StatusBarManager.WINDOW_STATE_SHOWING +import android.app.StatusBarManager.WINDOW_STATUS_BAR import android.view.LayoutInflater import android.view.MotionEvent import android.view.ViewTreeObserver @@ -31,12 +35,15 @@ import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeControllerImpl import com.android.systemui.shade.ShadeLogger import com.android.systemui.shade.ShadeViewController +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.view.ViewUtil import com.google.common.truth.Truth.assertThat @@ -75,6 +82,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var centralSurfacesImpl: CentralSurfacesImpl @Mock + private lateinit var commandQueue: CommandQueue + @Mock private lateinit var shadeControllerImpl: ShadeControllerImpl @Mock private lateinit var windowRootView: Provider<WindowRootView> @@ -82,6 +91,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { private lateinit var shadeLogger: ShadeLogger @Mock private lateinit var viewUtil: ViewUtil + private lateinit var statusBarWindowStateController: StatusBarWindowStateController private lateinit var view: PhoneStatusBarView private lateinit var controller: PhoneStatusBarViewController @@ -91,6 +101,9 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + + statusBarWindowStateController = StatusBarWindowStateController(DISPLAY_ID, commandQueue) + `when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController()) .thenReturn(moveFromCenterAnimation) // create the view and controller on main thread as it requires main looper @@ -186,6 +199,42 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { verify(shadeViewController, never()).handleExternalTouch(any()) } + @Test + fun onTouch_windowHidden_centralSurfacesNotNotified() { + val callback = getCommandQueueCallback() + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN) + + controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + + verify(centralSurfacesImpl, never()).setInteracting(any(), any()) + } + + @Test + fun onTouch_windowHiding_centralSurfacesNotNotified() { + val callback = getCommandQueueCallback() + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_HIDING) + + controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + + verify(centralSurfacesImpl, never()).setInteracting(any(), any()) + } + + @Test + fun onTouch_windowShowing_centralSurfacesNotified() { + val callback = getCommandQueueCallback() + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) + + controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) + + verify(centralSurfacesImpl).setInteracting(any(), any()) + } + + private fun getCommandQueueCallback(): CommandQueue.Callbacks { + val captor = argumentCaptor<CommandQueue.Callbacks>() + verify(commandQueue).addCallback(captor.capture()) + return captor.value!! + } + private fun createViewMock(): PhoneStatusBarView { val view = spy(view) val viewTreeObserver = mock(ViewTreeObserver::class.java) @@ -201,6 +250,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { featureFlags, userChipViewModel, centralSurfacesImpl, + statusBarWindowStateController, shadeControllerImpl, shadeViewController, windowRootView, @@ -218,4 +268,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { override var isHingeAngleEnabled: Boolean = false override val halfFoldedTimeoutMillis: Int = 0 } + + private companion object { + const val DISPLAY_ID = 1 + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java index 85052e600486..e4318659bef9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java @@ -64,7 +64,7 @@ import java.util.List; public class InflatedSmartRepliesTest extends SysuiTestCase { private static final Intent TEST_INTENT = new Intent("com.android.SMART_REPLY_VIEW_ACTION"); - private static final Intent WHITELISTED_TEST_INTENT = + private static final Intent ALLOWLISTED_TEST_INTENT = new Intent("com.android.WHITELISTED_TEST_ACTION"); @Mock private SmartReplyConstants mSmartReplyConstants; @@ -343,7 +343,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase { assertThat(smartReplyState.getSmartReplies().choices) .containsExactlyElementsIn(mEntry.getSmartReplies()).inOrder(); - // Since no apps are whitelisted no actions should be shown. + // Since no apps are allowlisted no actions should be shown. assertThat(smartReplyState.getSmartActions().actions).isEmpty(); assertThat(smartReplyState.getSuppressedActions()).isNull(); assertThat(smartReplyState.getHasPhishingAction()).isFalse(); @@ -358,7 +358,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase { allowedResolveInfo.activityInfo.packageName = allowedPackage; when(mPackageManagerWrapper .resolveActivity( - argThat(intent -> WHITELISTED_TEST_INTENT.getAction().equals( + argThat(intent -> ALLOWLISTED_TEST_INTENT.getAction().equals( intent.getAction())), anyInt() /* flags */)) .thenReturn(allowedResolveInfo); @@ -368,7 +368,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase { // suggestions. setupAppGeneratedReplies(null /* smartReplies */); ArrayList<Notification.Action> actions = new ArrayList<>(); - actions.add(createAction("allowed action", WHITELISTED_TEST_INTENT)); + actions.add(createAction("allowed action", ALLOWLISTED_TEST_INTENT)); actions.add(createAction("non-allowed action", TEST_INTENT)); modifyRanking(mEntry) @@ -379,7 +379,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase { InflatedSmartReplyState smartReplyState = mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry); - // Only the action for the whitelisted package should be allowed. + // Only the action for the allowlisted package should be allowed. assertThat(smartReplyState.getSmartActions().actions) .containsExactly(mEntry.getSmartActions().get(0)); assertThat(smartReplyState.getSuppressedActions()).isNull(); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 63a607c8d0d4..5b8bdd57ccbb 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -1413,7 +1413,7 @@ final class AutofillManagerServiceImpl Slog.v(TAG, "setAugmentedAutofillWhitelistLocked(packages=" + packages + ", activities=" + activities + ")"); } - whitelistForAugmentedAutofillPackages(packages, activities); + allowlistForAugmentedAutofillPackages(packages, activities); final String serviceName; if (mRemoteAugmentedAutofillServiceInfo != null) { serviceName = mRemoteAugmentedAutofillServiceInfo.getComponentName() @@ -1477,7 +1477,7 @@ final class AutofillManagerServiceImpl /** * @throws IllegalArgumentException if packages or components are empty. */ - private void whitelistForAugmentedAutofillPackages(@Nullable List<String> packages, + private void allowlistForAugmentedAutofillPackages(@Nullable List<String> packages, @Nullable List<ComponentName> components) { // TODO(b/123100824): add CTS test for when it's null synchronized (mLock) { diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 8cb7deaa00dc..b573800fde18 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -133,6 +133,7 @@ import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.backup.utils.BackupEligibilityRules; +import com.android.server.backup.utils.BackupManagerMonitorDumpsysUtils; import com.android.server.backup.utils.BackupManagerMonitorEventSender; import com.android.server.backup.utils.BackupObserverUtils; import com.android.server.backup.utils.SparseArrayUtils; @@ -142,6 +143,7 @@ import dalvik.annotation.optimization.NeverCompile; import com.google.android.collect.Sets; import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -150,6 +152,7 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -4161,6 +4164,7 @@ public class UserBackupManagerService { } } dumpInternal(pw); + dumpBMMEvents(pw); } finally { Binder.restoreCallingIdentity(identityToken); } @@ -4178,6 +4182,23 @@ public class UserBackupManagerService { } } + private void dumpBMMEvents(PrintWriter pw) { + BackupManagerMonitorDumpsysUtils bm = + new BackupManagerMonitorDumpsysUtils(); + File events = bm.getBMMEventsFile(); + pw.println("START OF BACKUP MANAGER MONITOR EVENTS"); + try (BufferedReader reader = new BufferedReader(new FileReader(events))) { + String line; + while ((line = reader.readLine()) != null) { + pw.println(line); + } + } catch (IOException e) { + Slog.e(TAG, "IO Exception when reading BMM events from file: " + e); + pw.println("IO Exception when reading BMM events from file"); + } + pw.println("END OF BACKUP MANAGER MONITOR EVENTS"); + } + @NeverCompile // Avoid size overhead of debugging code. private void dumpInternal(PrintWriter pw) { // Add prefix for only non-system users so that system user dumpsys is the same as before diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java new file mode 100644 index 000000000000..0b55ca21371b --- /dev/null +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java @@ -0,0 +1,260 @@ +/* + * 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.server.backup.utils; + +import android.app.backup.BackupAnnotations; +import android.app.backup.BackupManagerMonitor; +import android.app.backup.BackupRestoreEventLogger; +import android.os.Bundle; +import android.os.Environment; +import android.util.Slog; + +import com.android.internal.util.FastPrintWriter; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Map; + + +/* + * Util class to parse a BMM event and write it to a text file, to be the printed in + * the backup dumpsys + * + * Note: this class is note thread safe + */ +public class BackupManagerMonitorDumpsysUtils { + + private static final String TAG = "BackupManagerMonitorDumpsysUtils"; + // Name of the subdirectory where the text file containing the BMM events will be stored. + // Same as {@link UserBackupManagerFiles} + private static final String BACKUP_PERSISTENT_DIR = "backup"; + + /** + * Parses the BackupManagerMonitor bundle for a RESTORE event in a series of strings that + * will be persisted in a text file and printed in the dumpsys. + * + * If the evenntBundle passed is not a RESTORE event, return early + * + * Key information related to the event: + * - Timestamp (HAS TO ALWAYS BE THE FIRST LINE OF EACH EVENT) + * - Event ID + * - Event Category + * - Operation type + * - Package name (can be null) + * - Agent logs (if available) + * + * Example of formatting: + * RESTORE Event: [2023-08-18 17:16:00.735] Agent - Agent logging results + * Package name: com.android.wallpaperbackup + * Agent Logs: + * Data Type: wlp_img_system + * Item restored: 0/1 + * Agent Error - Category: no_wallpaper, Count: 1 + * Data Type: wlp_img_lock + * Item restored: 0/1 + * Agent Error - Category: no_wallpaper, Count: 1 + */ + public void parseBackupManagerMonitorRestoreEventForDumpsys(Bundle eventBundle) { + if (eventBundle == null) { + return; + } + + if (!isOpTypeRestore(eventBundle)) { + //We only log Restore events + return; + } + + if (!eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_ID) + || !eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY)) { + Slog.w(TAG, "Event id and category are not optional fields."); + return; + } + File bmmEvents = getBMMEventsFile(); + + try (FileOutputStream out = new FileOutputStream(bmmEvents, /*append*/ true); + PrintWriter pw = new FastPrintWriter(out);) { + + int eventCategory = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY); + int eventId = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID); + + if (eventId == BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS && + !hasAgentLogging(eventBundle)) { + // Do not record an empty agent logging event + return; + } + + pw.println("RESTORE Event: [" + timestamp() + "] " + + getCategory(eventCategory) + " - " + + getId(eventId)); + + if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME)) { + pw.println("\tPackage name: " + + eventBundle.getString(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME)); + } + + // TODO(b/296818666): add extras to the events + addAgentLogsIfAvailable(eventBundle, pw); + } catch (java.io.IOException e) { + Slog.e(TAG, "IO Exception when writing BMM events to file: " + e); + } + + } + + private boolean hasAgentLogging(Bundle eventBundle) { + if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS)) { + ArrayList<BackupRestoreEventLogger.DataTypeResult> agentLogs = + eventBundle.getParcelableArrayList( + BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS); + + return !agentLogs.isEmpty(); + } + return false; + } + + /** + * Extracts agent logs from the BackupManagerMonitor event. These logs detail: + * - the data type for the agent + * - the count of successfully restored items + * - the count of items that failed to restore + * - the metadata associated with this datatype + * - any errors + */ + private void addAgentLogsIfAvailable(Bundle eventBundle, PrintWriter pw) { + if (hasAgentLogging(eventBundle)) { + pw.println("\tAgent Logs:"); + ArrayList<BackupRestoreEventLogger.DataTypeResult> agentLogs = + eventBundle.getParcelableArrayList( + BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS); + for (BackupRestoreEventLogger.DataTypeResult result : agentLogs) { + int totalItems = result.getFailCount() + result.getSuccessCount(); + pw.println("\t\tData Type: " + result.getDataType()); + pw.println("\t\t\tItem restored: " + result.getSuccessCount() + "/" + + totalItems); + for (Map.Entry<String, Integer> entry : result.getErrors().entrySet()) { + pw.println("\t\t\tAgent Error - Category: " + + entry.getKey() + ", Count: " + entry.getValue()); + } + } + } + } + + /* + * Get the path of the text files which stores the BMM events + */ + public File getBMMEventsFile() { + File dataDir = new File(Environment.getDataDirectory(), BACKUP_PERSISTENT_DIR); + File fname = new File(dataDir, "bmmevents.txt"); + return fname; + } + + private String timestamp() { + long currentTime = System.currentTimeMillis(); + Date date = new Date(currentTime); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + return dateFormat.format(date); + } + + private String getCategory(int code) { + String category = switch (code) { + case BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT -> "Transport"; + case BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT -> "Agent"; + case BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY -> + "Backup Manager Policy"; + default -> "Unknown category code: " + code; + }; + return category; + } + + private String getId(int code) { + String id = switch (code) { + case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL -> "Full backup cancel"; + case BackupManagerMonitor.LOG_EVENT_ID_ILLEGAL_KEY -> "Illegal key"; + case BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND -> "No data to send"; + case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE -> "Package ineligible"; + case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT -> + "Package key-value participant"; + case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED -> "Package stopped"; + case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND -> "Package not found"; + case BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED -> "Backup disabled"; + case BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED -> + "Device not provisioned"; + case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT -> + "Package transport not present"; + case BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT -> "Error preflight"; + case BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT -> "Quota hit preflight"; + case BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP -> "Exception full backup"; + case BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL -> + "Key-value backup cancel"; + case BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE -> + "No restore metadata available"; + case BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED -> + "No PM metadata received"; + case BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA -> + "PM agent has no metadata"; + case BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT -> "Lost transport"; + case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT -> "Package not present"; + case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER -> + "Restore version higher"; + case BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT -> "App has no agent"; + case BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH -> "Signature mismatch"; + case BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT -> "Can't find agent"; + case BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT -> + "Key-value restore timeout"; + case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION -> "Restore any version"; + case BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH -> "Versions match"; + case BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER -> + "Version of backup older"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH -> + "Full restore signature mismatch"; + case BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT -> "System app no agent"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE -> + "Full restore allow backup false"; + case BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED -> "APK not installed"; + case BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK -> + "Cannot restore without APK"; + case BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE -> "Missing signature"; + case BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE -> + "Expected different package"; + case BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION -> "Unknown version"; + case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT -> "Full restore timeout"; + case BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST -> "Corrupt manifest"; + case BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH -> + "Widget metadata mismatch"; + case BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION -> + "Widget unknown version"; + case BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES -> "No packages"; + case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL -> "Transport is null"; + case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED -> + "Transport non-incremental backup required"; + case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS -> "Agent logging results"; + default -> "Unknown log event ID: " + code; + }; + return id; + } + + private boolean isOpTypeRestore(Bundle eventBundle) { + return switch (eventBundle.getInt( + BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, -1)) { + case BackupAnnotations.OperationType.RESTORE -> true; + default -> false; + }; + } +} diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java index f9286803f167..92e3107b6977 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java @@ -36,6 +36,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import java.util.List; @@ -54,9 +55,17 @@ public class BackupManagerMonitorEventSender { */ private static final int AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS = 500; @Nullable private IBackupManagerMonitor mMonitor; - + private final BackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtils; public BackupManagerMonitorEventSender(@Nullable IBackupManagerMonitor monitor) { mMonitor = monitor; + mBackupManagerMonitorDumpsysUtils = new BackupManagerMonitorDumpsysUtils(); + } + + @VisibleForTesting + BackupManagerMonitorEventSender(@Nullable IBackupManagerMonitor monitor, + BackupManagerMonitorDumpsysUtils backupManagerMonitorDumpsysUtils) { + mMonitor = monitor; + mBackupManagerMonitorDumpsysUtils = backupManagerMonitorDumpsysUtils; } public void setMonitor(IBackupManagerMonitor monitor) { @@ -82,29 +91,39 @@ public class BackupManagerMonitorEventSender { PackageInfo pkg, int category, Bundle extras) { - if (mMonitor != null) { - try { - Bundle bundle = new Bundle(); - bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, id); - bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, category); - if (pkg != null) { - bundle.putString(EXTRA_LOG_EVENT_PACKAGE_NAME, - pkg.packageName); - bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION, - pkg.versionCode); - bundle.putLong(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION, - pkg.getLongVersionCode()); - } - if (extras != null) { - bundle.putAll(extras); + try { + Bundle bundle = new Bundle(); + bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, id); + bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, category); + if (pkg != null) { + bundle.putString(EXTRA_LOG_EVENT_PACKAGE_NAME, + pkg.packageName); + bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION, + pkg.versionCode); + bundle.putLong(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION, + pkg.getLongVersionCode()); + } + if (extras != null) { + bundle.putAll(extras); + if (extras.containsKey(EXTRA_LOG_OPERATION_TYPE) && + extras.getInt(EXTRA_LOG_OPERATION_TYPE) == OperationType.RESTORE){ + mBackupManagerMonitorDumpsysUtils + .parseBackupManagerMonitorRestoreEventForDumpsys(bundle); } + } + + if (mMonitor != null) { mMonitor.onEvent(bundle); - } catch (RemoteException e) { - mMonitor = null; + } else { if (DEBUG) { - Slog.w(TAG, "backup manager monitor went away"); + Slog.w(TAG, "backup manager monitor is null unable to send event"); } } + } catch (RemoteException e) { + mMonitor = null; + if (DEBUG) { + Slog.w(TAG, "backup manager monitor went away"); + } } } @@ -120,7 +139,7 @@ public class BackupManagerMonitorEventSender { */ public void monitorAgentLoggingResults(PackageInfo pkg, IBackupAgent agent) { if (mMonitor == null) { - return; + Slog.i(TAG, "backup manager monitor is null unable to send event"+pkg); } try { diff --git a/services/companion/Android.bp b/services/companion/Android.bp index a248d9e55a8a..25f57b3c5331 100644 --- a/services/companion/Android.bp +++ b/services/companion/Android.bp @@ -26,5 +26,6 @@ java_library_static { ], static_libs: [ "ukey2_jni", + "virtualdevice_flags_lib", ], } diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp index 50a09b9a8ea9..6526c78d5124 100644 --- a/services/companion/java/com/android/server/companion/virtual/Android.bp +++ b/services/companion/java/com/android/server/companion/virtual/Android.bp @@ -9,4 +9,4 @@ aconfig_declarations { srcs: [ "flags.aconfig", ], -}
\ No newline at end of file +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index d1be5a9e971d..f23fe2a7bce4 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -126,6 +126,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final VirtualDeviceManagerService mService; private final PendingTrampolineCallback mPendingTrampolineCallback; private final int mOwnerUid; + private final VirtualDeviceLog mVirtualDeviceLog; private final String mOwnerPackageName; private int mDeviceId; private @Nullable String mPersistentDeviceId; @@ -197,6 +198,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub Context context, AssociationInfo associationInfo, VirtualDeviceManagerService service, + VirtualDeviceLog virtualDeviceLog, IBinder token, AttributionSource attributionSource, int deviceId, @@ -210,6 +212,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub context, associationInfo, service, + virtualDeviceLog, token, attributionSource, deviceId, @@ -228,6 +231,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub Context context, AssociationInfo associationInfo, VirtualDeviceManagerService service, + VirtualDeviceLog virtualDeviceLog, IBinder token, AttributionSource attributionSource, int deviceId, @@ -240,6 +244,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub VirtualDeviceParams params, DisplayManagerGlobal displayManager) { super(PermissionEnforcer.fromContext(context)); + mVirtualDeviceLog = virtualDeviceLog; mOwnerPackageName = attributionSource.getPackageName(); UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid()); mContext = context.createContextAsUser(ownerUserHandle, 0); @@ -271,6 +276,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + mVirtualDeviceLog.logCreated(deviceId, mOwnerUid); } @VisibleForTesting @@ -405,6 +411,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub super.close_enforcePermission(); // Remove about-to-be-closed virtual device from the service before butchering it. boolean removed = mService.removeVirtualDevice(mDeviceId); + mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid); mDeviceId = Context.DEVICE_ID_INVALID; // Device is already closed. diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java new file mode 100644 index 000000000000..c65aa5bd355b --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java @@ -0,0 +1,152 @@ +/* + * 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.server.companion.virtual; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.util.SparseArray; + +import java.io.PrintWriter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayDeque; + +final class VirtualDeviceLog { + public static int TYPE_CREATED = 0x0; + public static int TYPE_CLOSED = 0x1; + + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern( + "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); + private static final int MAX_ENTRIES = 16; + + private final Context mContext; + private final ArrayDeque<LogEntry> mLogEntries = new ArrayDeque<>(); + + VirtualDeviceLog(Context context) { + mContext = context; + } + + void logCreated(int deviceId, int ownerUid) { + final long token = Binder.clearCallingIdentity(); + try { + if (!Flags.dumpHistory()) { + return; + } + addEntry(new LogEntry(TYPE_CREATED, deviceId, System.currentTimeMillis(), ownerUid)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + void logClosed(int deviceId, int ownerUid) { + final long token = Binder.clearCallingIdentity(); + try { + if (!Flags.dumpHistory()) { + return; + } + addEntry(new LogEntry(TYPE_CLOSED, deviceId, System.currentTimeMillis(), ownerUid)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void addEntry(LogEntry entry) { + mLogEntries.push(entry); + if (mLogEntries.size() > MAX_ENTRIES) { + mLogEntries.removeLast(); + } + } + + void dump(PrintWriter pw) { + final long token = Binder.clearCallingIdentity(); + try { + if (!Flags.dumpHistory()) { + return; + } + pw.println("VirtualDevice Log:"); + UidToPackageNameCache packageNameCache = new UidToPackageNameCache( + mContext.getPackageManager()); + for (LogEntry logEntry : mLogEntries) { + logEntry.dump(pw, " ", packageNameCache); + + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + static class LogEntry { + private final int mType; + private final int mDeviceId; + private final long mTimestamp; + private final int mOwnerUid; + + LogEntry(int type, int deviceId, long timestamp, int ownerUid) { + this.mType = type; + this.mDeviceId = deviceId; + this.mTimestamp = timestamp; + this.mOwnerUid = ownerUid; + } + + void dump(PrintWriter pw, String prefix, UidToPackageNameCache packageNameCache) { + StringBuilder sb = new StringBuilder(prefix); + sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(mTimestamp))); + sb.append(" - "); + sb.append(mType == TYPE_CREATED ? "START" : "CLOSE"); + sb.append(" Device ID: "); + sb.append(mDeviceId); + sb.append(" "); + sb.append(mOwnerUid); + sb.append(" ("); + sb.append(packageNameCache.getPackageName(mOwnerUid)); + sb.append(")"); + pw.println(sb); + } + } + + private static class UidToPackageNameCache { + private final PackageManager mPackageManager; + private final SparseArray<String> mUidToPackagesCache = new SparseArray<>(); + + public UidToPackageNameCache(PackageManager packageManager) { + mPackageManager = packageManager; + } + + String getPackageName(int ownerUid) { + String[] packages; + if (mUidToPackagesCache.contains(ownerUid)) { + return mUidToPackagesCache.get(ownerUid); + } else { + packages = mPackageManager.getPackagesForUid(ownerUid); + String packageName = ""; + if (packages != null && packages.length > 0) { + packageName = packages[0]; + if (packages.length > 1) { + StringBuilder sb = new StringBuilder(); + sb.append(packageName) + .append(",..."); + packageName = sb.toString(); + } + } + mUidToPackagesCache.put(ownerUid, packageName); + return packageName; + } + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index e558498e4715..7429fbe18a0c 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -85,6 +85,7 @@ public class VirtualDeviceManagerService extends SystemService { private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; private final VirtualDeviceManagerInternal mLocalService; + private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext()); private final Handler mHandler = new Handler(Looper.getMainLooper()); private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler); @@ -344,9 +345,11 @@ public class VirtualDeviceManagerService extends SystemService { final Consumer<ArraySet<Integer>> runningAppsChangedCallback = runningUids -> notifyRunningAppsChanged(deviceId, runningUids); VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), associationInfo, - VirtualDeviceManagerService.this, token, attributionSource, deviceId, + VirtualDeviceManagerService.this, mVirtualDeviceLog, token, attributionSource, + deviceId, cameraAccessController, mPendingTrampolineCallback, activityListener, soundEffectListener, runningAppsChangedCallback, params); + synchronized (mVirtualDeviceManagerLock) { if (mVirtualDevices.size() == 0) { final long callindId = Binder.clearCallingIdentity(); @@ -521,6 +524,8 @@ public class VirtualDeviceManagerService extends SystemService { for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { virtualDevicesSnapshot.get(i).dump(fd, fout, args); } + + mVirtualDeviceLog.dump(fout); } } diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 173a75be10b0..7907d616d1b5 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -80,9 +80,9 @@ import android.util.apk.ApkSignatureVerifier; import android.util.apk.ApkSigningBlockUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.modules.expresslog.Histogram; import com.android.internal.os.IBinaryTransparencyService; import com.android.internal.util.FrameworkStatsLog; +import com.android.modules.expresslog.Histogram; import com.android.server.pm.ApexManager; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageSplit; @@ -1391,7 +1391,7 @@ public class BinaryTransparencyService extends SystemService { // Check the flag to determine whether biometric property verification is enabled. It's // disabled by default. if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BIOMETRICS, - KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, false)) { + KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, true)) { if (DEBUG) { Slog.d(TAG, "Do not collect/verify biometric properties. Feature disabled by " + "DeviceConfig"); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 7fb9580b28ab..4d249abafdf1 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -73,8 +73,8 @@ import android.content.pm.ProviderInfo; import android.content.pm.UserInfo; import android.content.res.ObbInfo; import android.database.ContentObserver; -import android.media.MediaCodecList; import android.media.MediaCodecInfo; +import android.media.MediaCodecList; import android.media.MediaFormat; import android.net.Uri; import android.os.BatteryManager; @@ -219,6 +219,8 @@ class StorageManagerService extends IStorageManager.Stub @GuardedBy("mLock") private final Set<Integer> mCeStoragePreparedUsers = new ArraySet<>(); + private volatile long mInternalStorageSize = 0; + public static class Lifecycle extends SystemService { private StorageManagerService mStorageManagerService; @@ -3519,6 +3521,15 @@ class StorageManagerService extends IStorageManager.Stub return authority; } + @Override + public long getInternalStorageBlockDeviceSize() throws RemoteException { + if (mInternalStorageSize == 0) { + mInternalStorageSize = mVold.getStorageSize(); + } + + return mInternalStorageSize; + } + /** * Enforces that the caller is the {@link ExternalStorageService} * diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index d3bfc9ab6209..942d35a1d842 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2514,30 +2514,36 @@ final class ActivityManagerShellCommand extends ShellCommand { StopUserCallback callback = wait ? new StopUserCallback(userId) : null; Slogf.d(TAG, "Calling stopUser(%d, %b, %s)", userId, force, callback); - int res = mInterface.stopUser(userId, force, callback); - if (res != ActivityManager.USER_OP_SUCCESS) { - String txt = ""; - switch (res) { - case ActivityManager.USER_OP_IS_CURRENT: - txt = " (Can't stop current user)"; - break; - case ActivityManager.USER_OP_UNKNOWN_USER: - txt = " (Unknown user " + userId + ")"; - break; - case ActivityManager.USER_OP_ERROR_IS_SYSTEM: - txt = " (System user cannot be stopped)"; - break; - case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP: - txt = " (Can't stop user " + userId - + " - one of its related users can't be stopped)"; - break; + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "shell_runStopUser-" + userId + "-[stopUser]"); + try { + int res = mInterface.stopUser(userId, force, callback); + if (res != ActivityManager.USER_OP_SUCCESS) { + String txt = ""; + switch (res) { + case ActivityManager.USER_OP_IS_CURRENT: + txt = " (Can't stop current user)"; + break; + case ActivityManager.USER_OP_UNKNOWN_USER: + txt = " (Unknown user " + userId + ")"; + break; + case ActivityManager.USER_OP_ERROR_IS_SYSTEM: + txt = " (System user cannot be stopped)"; + break; + case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP: + txt = " (Can't stop user " + userId + + " - one of its related users can't be stopped)"; + break; + } + getErrPrintWriter().println("Switch failed: " + res + txt); + return -1; + } else if (callback != null) { + callback.waitForFinish(); } - getErrPrintWriter().println("Switch failed: " + res + txt); - return -1; - } else if (callback != null) { - callback.waitForFinish(); + return 0; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } - return 0; } int runIsUserStopped(PrintWriter pw) { diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 4cc147fcea03..9219623a031d 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -293,7 +293,8 @@ public class ContentProviderHelper { return holder; } - // Don't expose providers between normal apps and instant apps + // Don't expose providers between normal apps and instant apps; enforce limited + // package visibility (introduced in Android 11); etc. try { if (AppGlobals.getPackageManager() .resolveContentProvider(name, /*flags=*/ 0, userId) == null) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d426c797b981..27708330efd3 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -105,6 +105,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; @@ -921,13 +922,24 @@ class UserController implements Handler.Callback { int stopUser(final int userId, final boolean force, boolean allowDelayedLocking, final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) { - checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser"); - Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId); + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); - enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId); - synchronized (mLock) { - return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback, - keyEvictedCallback); + t.traceBegin("UserController" + + (force ? "-force" : "") + + (allowDelayedLocking ? "-allowDelayedLocking" : "") + + (stopUserCallback != null ? "-withStopUserCallback" : "") + + "-" + userId + "-[stopUser]"); + try { + checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser"); + Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId); + + enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId); + synchronized (mLock) { + return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback, + keyEvictedCallback); + } + } finally { + t.traceEnd(); } } @@ -944,10 +956,10 @@ class UserController implements Handler.Callback { if (isCurrentUserLU(userId)) { return USER_OP_IS_CURRENT; } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); int[] usersToStop = getUsersToStopLU(userId); // If one of related users is system or current, no related users should be stopped - for (int i = 0; i < usersToStop.length; i++) { - int relatedUserId = usersToStop[i]; + for (int relatedUserId : usersToStop) { if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) { if (DEBUG_MU) { Slogf.i(TAG, "stopUsersLocked cannot stop related user " + relatedUserId); @@ -956,8 +968,10 @@ class UserController implements Handler.Callback { if (force) { Slogf.i(TAG, "Force stop user " + userId + ". Related users will not be stopped"); + t.traceBegin("stopSingleUserLU-force-" + userId + "-[stopUser]"); stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback, keyEvictedCallback); + t.traceEnd(); return USER_OP_SUCCESS; } return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; @@ -965,9 +979,11 @@ class UserController implements Handler.Callback { } if (DEBUG_MU) Slogf.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop)); for (int userIdToStop : usersToStop) { + t.traceBegin("stopSingleUserLU-" + userIdToStop + "-[stopUser]"); stopSingleUserLU(userIdToStop, allowDelayedLocking, userIdToStop == userId ? stopUserCallback : null, userIdToStop == userId ? keyEvictedCallback : null); + t.traceEnd(); } return USER_OP_SUCCESS; } @@ -1047,14 +1063,24 @@ class UserController implements Handler.Callback { && uss.state != UserState.STATE_SHUTDOWN) { uss.setState(UserState.STATE_STOPPING); UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal(); + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("setUserState-STATE_STOPPING-" + userId + "-[stopUser]"); userManagerInternal.setUserState(userId, uss.state); + t.traceEnd(); + t.traceBegin("unassignUserFromDisplayOnStop-" + userId + "-[stopUser]"); userManagerInternal.unassignUserFromDisplayOnStop(userId); + t.traceEnd(); updateStartedUserArrayLU(); final boolean allowDelayedLockingCopied = allowDelayedLocking; Runnable finishUserStoppingAsync = () -> - mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied)); + mHandler.post(() -> { + TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(); + t2.traceBegin("finishUserStopping-" + userId + "-[stopUser]"); + finishUserStopping(userId, uss, allowDelayedLockingCopied); + t2.traceEnd(); + }); if (mInjector.getUserManager().isPreCreated(userId)) { finishUserStoppingAsync.run(); @@ -1075,12 +1101,18 @@ class UserController implements Handler.Callback { @Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { + asyncTraceEnd("broadcast-ACTION_USER_STOPPING-" + userId + "-[stopUser]", + userId); finishUserStoppingAsync.run(); } }; + TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(); + t2.traceBegin("clearBroadcastQueueForUser-" + userId + "-[stopUser]"); // Clear broadcast queue for the user to avoid delivering stale broadcasts mInjector.clearBroadcastQueueForUser(userId); + t2.traceEnd(); + asyncTraceBegin("broadcast-ACTION_USER_STOPPING-" + userId + "-[stopUser]", userId); // Kick things off. mInjector.broadcastIntent(stoppingIntent, null, stoppingReceiver, 0, null, null, @@ -1111,7 +1143,10 @@ class UserController implements Handler.Callback { } uss.setState(UserState.STATE_SHUTDOWN); } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("setUserState-STATE_SHUTDOWN-" + userId + "-[stopUser]"); mInjector.getUserManagerInternal().setUserState(userId, uss.state); + t.traceEnd(); mInjector.batteryStatsServiceNoteEvent( BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH, @@ -1119,7 +1154,12 @@ class UserController implements Handler.Callback { mInjector.getSystemServiceManager().onUserStopping(userId); Runnable finishUserStoppedAsync = () -> - mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking)); + mHandler.post(() -> { + TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(); + t2.traceBegin("finishUserStopped-" + userId + "-[stopUser]"); + finishUserStopped(uss, allowDelayedLocking); + t2.traceEnd(); + }); if (mInjector.getUserManager().isPreCreated(userId)) { finishUserStoppedAsync.run(); return; @@ -1132,9 +1172,11 @@ class UserController implements Handler.Callback { @Override public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { + asyncTraceEnd("broadcast-ACTION_SHUTDOWN-" + userId + "-[stopUser]", userId); finishUserStoppedAsync.run(); } }; + asyncTraceBegin("broadcast-ACTION_SHUTDOWN-" + userId + "-[stopUser]", userId); mInjector.broadcastIntent(shutdownIntent, null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE, @@ -1185,6 +1227,7 @@ class UserController implements Handler.Callback { } } } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); if (stopped) { Slogf.i(TAG, "Removing user state from UserManager.mUserStates for user #" + userId + " as a result of user being stopped"); @@ -1193,20 +1236,33 @@ class UserController implements Handler.Callback { mInjector.activityManagerOnUserStopped(userId); // Clean up all state and processes associated with the user. // Kill all the processes for the user. + t.traceBegin("forceStopUser-" + userId + "-[stopUser]"); forceStopUser(userId, "finish user"); + t.traceEnd(); } for (final IStopUserCallback callback : stopCallbacks) { try { - if (stopped) callback.userStopped(userId); - else callback.userStopAborted(userId); + if (stopped) { + t.traceBegin("stopCallbacks.userStopped-" + userId + "-[stopUser]"); + callback.userStopped(userId); + t.traceEnd(); + } else { + t.traceBegin("stopCallbacks.userStopAborted-" + userId + "-[stopUser]"); + callback.userStopAborted(userId); + t.traceEnd(); + } } catch (RemoteException ignored) { } } if (stopped) { + t.traceBegin("systemServiceManagerOnUserStopped-" + userId + "-[stopUser]"); mInjector.systemServiceManagerOnUserStopped(userId); + t.traceEnd(); + t.traceBegin("taskSupervisorRemoveUser-" + userId + "-[stopUser]"); mInjector.taskSupervisorRemoveUser(userId); + t.traceEnd(); // Remove the user if it is ephemeral. if (userInfo.isEphemeral() && !userInfo.preCreated) { @@ -3361,6 +3417,16 @@ class UserController implements Handler.Callback { return DEFAULT_USER_SWITCH_TIMEOUT_MS; } + private static void asyncTraceBegin(String msg, int cookie) { + Slogf.d(TAG, "%s - asyncTraceBegin(%d)", msg, cookie); + Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg, cookie); + } + + private static void asyncTraceEnd(String msg, int cookie) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg, cookie); + Slogf.d(TAG, "%s - asyncTraceEnd(%d)", msg, cookie); + } + /** * Uptime when any user was being unlocked most recently. 0 if no users have been unlocked * yet. To avoid lock contention (since it's used by OomAdjuster), it's volatile internally. @@ -3475,12 +3541,16 @@ class UserController implements Handler.Callback { ordered = false; } + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); // TODO b/64165549 Verify that mLock is not held before calling AMS methods synchronized (mService) { - return mService.broadcastIntentLocked(null, null, null, intent, resolvedType, - resultTo, resultCode, resultData, resultExtras, requiredPermissions, null, - null, appOp, bOptions, ordered, sticky, callingPid, callingUid, - realCallingUid, realCallingPid, userId); + t.traceBegin("broadcastIntent-" + userId + "-" + intent.getAction()); + final int result = mService.broadcastIntentLocked(null, null, null, intent, + resolvedType, resultTo, resultCode, resultData, resultExtras, + requiredPermissions, null, null, appOp, bOptions, ordered, sticky, + callingPid, callingUid, realCallingUid, realCallingPid, userId); + t.traceEnd(); + return result; } } diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java index 97e5c6fbd8c3..356b30103c2f 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.UserHandle; +import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -54,6 +55,7 @@ public class AuthenticationStatsCollector { private final float mThreshold; private final int mModality; + private boolean mPersisterInitialized = false; @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap; @@ -85,9 +87,15 @@ public class AuthenticationStatsCollector { } private void initializeUserAuthenticationStatsMap() { - mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext); - for (AuthenticationStats stats : mAuthenticationStatsPersister.getAllFrrStats(mModality)) { - mUserAuthenticationStatsMap.put(stats.getUserId(), stats); + try { + mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext); + for (AuthenticationStats stats : + mAuthenticationStatsPersister.getAllFrrStats(mModality)) { + mUserAuthenticationStatsMap.put(stats.getUserId(), stats); + } + mPersisterInitialized = true; + } catch (IllegalStateException e) { + Slog.w(TAG, "Failed to initialize AuthenticationStatsPersister.", e); } } @@ -108,7 +116,9 @@ public class AuthenticationStatsCollector { authenticationStats.authenticate(authenticated); - persistDataIfNeeded(userId); + if (mPersisterInitialized) { + persistDataIfNeeded(userId); + } sendNotificationIfNeeded(userId); } @@ -166,11 +176,13 @@ public class AuthenticationStatsCollector { } private void onUserRemoved(final int userId) { - if (mAuthenticationStatsPersister == null) { + if (!mPersisterInitialized) { initializeUserAuthenticationStatsMap(); } - mUserAuthenticationStatsMap.remove(userId); - mAuthenticationStatsPersister.removeFrrStats(userId); + if (mPersisterInitialized) { + mUserAuthenticationStatsMap.remove(userId); + mAuthenticationStatsPersister.removeFrrStats(userId); + } } /** diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index d647757442e0..80c3a270efdf 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -119,6 +119,8 @@ public class AutomaticBrightnessController { // hysteresis threshold. private final long mBrighteningLightDebounceConfig; private final long mDarkeningLightDebounceConfig; + private final long mBrighteningLightDebounceConfigIdle; + private final long mDarkeningLightDebounceConfigIdle; // If true immediately after the screen is turned on the controller will try to adjust the // brightness based on the current sensor reads. If false, the controller will collect more data @@ -253,6 +255,7 @@ public class AutomaticBrightnessController { int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, @@ -265,7 +268,8 @@ public class AutomaticBrightnessController { interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, - darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, + darkeningLightDebounceConfig, brighteningLightDebounceConfigIdle, + darkeningLightDebounceConfigIdle, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessModeController, brightnessThrottler, idleModeBrightnessMapper, @@ -280,6 +284,7 @@ public class AutomaticBrightnessController { int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, HysteresisLevels ambientBrightnessThresholdsIdle, @@ -303,6 +308,8 @@ public class AutomaticBrightnessController { mCurrentLightSensorRate = -1; mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; + mBrighteningLightDebounceConfigIdle = brighteningLightDebounceConfigIdle; + mDarkeningLightDebounceConfigIdle = darkeningLightDebounceConfigIdle; mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; mAmbientLightHorizonLong = ambientLightHorizonLong; mAmbientLightHorizonShort = ambientLightHorizonShort; @@ -366,9 +373,10 @@ public class AutomaticBrightnessController { } /** - * @return The current brightness recommendation calculated from the current conditions. - * @param brightnessEvent Event object to populate with details about why the specific - * brightness was chosen. + * @param brightnessEvent Holds details about how the brightness is calculated. + * + * @return The current automatic brightness recommended value. Populates brightnessEvent + * parameters with details about how the brightness was calculated. */ public float getAutomaticScreenBrightness(BrightnessEvent brightnessEvent) { if (brightnessEvent != null) { @@ -560,6 +568,8 @@ public class AutomaticBrightnessController { pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); + pw.println(" mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle); + pw.println(" mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle); pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); @@ -820,7 +830,8 @@ public class AutomaticBrightnessController { } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } - return earliestValidTime + mBrighteningLightDebounceConfig; + return earliestValidTime + (isInIdleMode() + ? mBrighteningLightDebounceConfigIdle : mBrighteningLightDebounceConfig); } private long nextAmbientLightDarkeningTransition(long time) { @@ -832,7 +843,8 @@ public class AutomaticBrightnessController { } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } - return earliestValidTime + mDarkeningLightDebounceConfig; + return earliestValidTime + (isInIdleMode() + ? mDarkeningLightDebounceConfigIdle : mDarkeningLightDebounceConfig); } private void updateAmbientLux() { diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 46cd496bdcd3..0d6635d5b6e4 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -40,6 +40,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.window.ScreenCapture; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; @@ -138,8 +139,13 @@ final class ColorFade { public static final int MODE_FADE = 2; public ColorFade(int displayId) { + this(displayId, LocalServices.getService(DisplayManagerInternal.class)); + } + + @VisibleForTesting + ColorFade(int displayId, DisplayManagerInternal displayManagerInternal) { mDisplayId = displayId; - mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mDisplayManagerInternal = displayManagerInternal; } /** diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 01eceda202d2..c0c60a47263a 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -184,6 +184,7 @@ import javax.xml.datatype.DatatypeConfigurationException; * <defaultRefreshRateInHbmSunlight>75</defaultRefreshRateInHbmSunlight> * <lowerBlockingZoneConfigs> * <defaultRefreshRate>75</defaultRefreshRate> + * <refreshRateThermalThrottlingId>id_of_a_throttling_map</refreshRateThermalThrottlingId> * <blockingZoneThreshold> * <displayBrightnessPoint> * <lux>50</lux> @@ -252,13 +253,19 @@ import javax.xml.datatype.DatatypeConfigurationException; * <quirk>canSetBrightnessViaHwc</quirk> * </quirks> * - * <autoBrightness enable="true"> + * <autoBrightness enabled="true"> * <brighteningLightDebounceMillis> * 2000 * </brighteningLightDebounceMillis> * <darkeningLightDebounceMillis> - * 1000 + * 4000 * </darkeningLightDebounceMillis> + * <brighteningLightDebounceIdleMillis> + * 2000 + * </brighteningLightDebounceIdleMillis> + * <darkeningLightDebounceIdleMillis> + * 1000 + * </darkeningLightDebounceIdleMillis> * <displayBrightnessMapping> * <displayBrightnessPoint> * <lux>50</lux> @@ -649,6 +656,14 @@ public class DisplayDeviceConfig { private long mAutoBrightnessDarkeningLightDebounce = INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; + // Represents the auto-brightness brightening light debounce for idle screen brightness mode. + private long mAutoBrightnessBrighteningLightDebounceIdle = + INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; + + // Represents the auto-brightness darkening light debounce for idle screen brightness mode. + private long mAutoBrightnessDarkeningLightDebounceIdle = + INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; + // This setting allows non-default displays to have autobrightness enabled. private boolean mAutoBrightnessAvailable = false; // This stores the raw value loaded from the config file - true if not written. @@ -732,6 +747,12 @@ public class DisplayDeviceConfig { private float[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS; private float[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS; + /** + * Thermal throttling maps for the low and high blocking zones. + */ + private String mLowBlockingZoneThermalMapId = null; + private String mHighBlockingZoneThermalMapId = null; + private final HashMap<String, ThermalBrightnessThrottlingData> mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>(); @@ -1440,6 +1461,20 @@ public class DisplayDeviceConfig { } /** + * @return Auto brightness darkening light debounce for idle screen brightness mode + */ + public long getAutoBrightnessDarkeningLightDebounceIdle() { + return mAutoBrightnessDarkeningLightDebounceIdle; + } + + /** + * @return Auto brightness brightening light debounce for idle screen brightness mode + */ + public long getAutoBrightnessBrighteningLightDebounceIdle() { + return mAutoBrightnessBrighteningLightDebounceIdle; + } + + /** * @return Auto brightness brightening ambient lux levels */ public float[] getAutoBrightnessBrighteningLevelsLux() { @@ -1536,6 +1571,13 @@ public class DisplayDeviceConfig { } /** + * @return The refresh rate thermal map for low blocking zone. + */ + public SparseArray<SurfaceControl.RefreshRateRange> getLowBlockingZoneThermalMap() { + return getThermalRefreshRateThrottlingData(mLowBlockingZoneThermalMapId); + } + + /** * @return An array of high display brightness thresholds. This, in combination with high * ambient brightness thresholds help define buckets in which the refresh rate switching is not * allowed. @@ -1558,6 +1600,13 @@ public class DisplayDeviceConfig { } /** + * @return The refresh rate thermal map for high blocking zone. + */ + public SparseArray<SurfaceControl.RefreshRateRange> getHighBlockingZoneThermalMap() { + return getThermalRefreshRateThrottlingData(mHighBlockingZoneThermalMapId); + } + + /** * @return A mapping from screen off brightness sensor readings to lux values. This estimates * the ambient lux when the screen is off to determine the initial brightness */ @@ -1664,6 +1713,10 @@ public class DisplayDeviceConfig { + mAutoBrightnessBrighteningLightDebounce + ", mAutoBrightnessDarkeningLightDebounce= " + mAutoBrightnessDarkeningLightDebounce + + ", mAutoBrightnessBrighteningLightDebounceIdle= " + + mAutoBrightnessBrighteningLightDebounceIdle + + ", mAutoBrightnessDarkeningLightDebounceIdle= " + + mAutoBrightnessDarkeningLightDebounceIdle + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux) + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits) + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable @@ -1677,6 +1730,8 @@ public class DisplayDeviceConfig { + ", mDefaultRefreshRateInHbmHdr= " + mDefaultRefreshRateInHbmHdr + ", mDefaultRefreshRateInHbmSunlight= " + mDefaultRefreshRateInHbmSunlight + ", mRefreshRateThrottlingMap= " + mRefreshRateThrottlingMap + + ", mLowBlockingZoneThermalMapId= " + mLowBlockingZoneThermalMapId + + ", mHighBlockingZoneThermalMapId= " + mHighBlockingZoneThermalMapId + "\n" + "mLowDisplayBrightnessThresholds= " + Arrays.toString(mLowDisplayBrightnessThresholds) @@ -2127,9 +2182,13 @@ public class DisplayDeviceConfig { } /** - * Loads the refresh rate configurations pertaining to the upper blocking zones. + * Loads the refresh rate configurations pertaining to the lower blocking zones. */ private void loadLowerRefreshRateBlockingZones(BlockingZoneConfig lowerBlockingZoneConfig) { + if (lowerBlockingZoneConfig != null) { + mLowBlockingZoneThermalMapId = + lowerBlockingZoneConfig.getRefreshRateThermalThrottlingId(); + } loadLowerBlockingZoneDefaultRefreshRate(lowerBlockingZoneConfig); loadLowerBrightnessThresholds(lowerBlockingZoneConfig); } @@ -2138,6 +2197,10 @@ public class DisplayDeviceConfig { * Loads the refresh rate configurations pertaining to the upper blocking zones. */ private void loadHigherRefreshRateBlockingZones(BlockingZoneConfig upperBlockingZoneConfig) { + if (upperBlockingZoneConfig != null) { + mHighBlockingZoneThermalMapId = + upperBlockingZoneConfig.getRefreshRateThermalThrottlingId(); + } loadHigherBlockingZoneDefaultRefreshRate(upperBlockingZoneConfig); loadHigherBrightnessThresholds(upperBlockingZoneConfig); } @@ -2277,6 +2340,9 @@ public class DisplayDeviceConfig { final AutoBrightness autoBrightness = config.getAutoBrightness(); loadAutoBrightnessBrighteningLightDebounce(autoBrightness); loadAutoBrightnessDarkeningLightDebounce(autoBrightness); + // Idle must be called after interactive, since we fall back to it if needed. + loadAutoBrightnessBrighteningLightDebounceIdle(autoBrightness); + loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness); loadAutoBrightnessDisplayBrightnessMapping(autoBrightness); loadEnableAutoBrightness(autoBrightness); } @@ -2312,6 +2378,37 @@ public class DisplayDeviceConfig { } /** + * Loads the auto-brightness brightening light debounce for idle mode. Internally, this takes + * care of loading the value from the display config, and if not present, falls back to + * whichever interactive value was chosen. + */ + private void loadAutoBrightnessBrighteningLightDebounceIdle( + AutoBrightness autoBrightnessConfig) { + if (autoBrightnessConfig == null + || autoBrightnessConfig.getBrighteningLightDebounceIdleMillis() == null) { + mAutoBrightnessBrighteningLightDebounceIdle = mAutoBrightnessBrighteningLightDebounce; + } else { + mAutoBrightnessBrighteningLightDebounceIdle = + autoBrightnessConfig.getBrighteningLightDebounceIdleMillis().intValue(); + } + } + + /** + * Loads the auto-brightness darkening light debounce for idle mode. Internally, this takes + * care of loading the value from the display config, and if not present, falls back to + * whichever interactive value was chosen. + */ + private void loadAutoBrightnessDarkeningLightDebounceIdle(AutoBrightness autoBrightnessConfig) { + if (autoBrightnessConfig == null + || autoBrightnessConfig.getDarkeningLightDebounceIdleMillis() == null) { + mAutoBrightnessDarkeningLightDebounceIdle = mAutoBrightnessDarkeningLightDebounce; + } else { + mAutoBrightnessDarkeningLightDebounceIdle = + autoBrightnessConfig.getDarkeningLightDebounceIdleMillis().intValue(); + } + } + + /** * Loads the auto-brightness display brightness mappings. Internally, this takes care of * loading the value from the display config, and if not present, falls back to config.xml. */ diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index dd22cfd1f294..79b73430b934 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1232,6 +1232,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call .getAutoBrightnessBrighteningLightDebounce(); long darkeningLightDebounce = mDisplayDeviceConfig .getAutoBrightnessDarkeningLightDebounce(); + long brighteningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounceIdle(); + long darkeningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounceIdle(); boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); @@ -1271,7 +1275,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, - darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, + darkeningLightDebounce, brighteningLightDebounceIdle, + darkeningLightDebounceIdle, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper, @@ -1906,6 +1911,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final float currentBrightness = mPowerState.getScreenBrightness(); final float currentSdrBrightness = mPowerState.getSdrScreenBrightness(); + if (isValidBrightnessValue(animateValue) && (animateValue != currentBrightness || sdrAnimateValue != currentSdrBrightness)) { @@ -3536,6 +3542,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, @@ -3549,6 +3556,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, + brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessRangeController, diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 0f89a6e04cf9..6c2240becbff 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -1043,6 +1043,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal .getAutoBrightnessBrighteningLightDebounce(); long darkeningLightDebounce = mDisplayDeviceConfig .getAutoBrightnessDarkeningLightDebounce(); + long brighteningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessBrighteningLightDebounceIdle(); + long darkeningLightDebounceIdle = mDisplayDeviceConfig + .getAutoBrightnessDarkeningLightDebounceIdle(); boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); @@ -1082,7 +1086,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, - darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, + darkeningLightDebounce, brighteningLightDebounceIdle, + darkeningLightDebounceIdle, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, mBrightnessThrottler, mIdleModeBrightnessMapper, @@ -2886,6 +2891,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, @@ -2899,6 +2905,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, + brighteningLightDebounceConfigIdle, darkeningLightDebounceConfigIdle, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, context, brightnessModeController, diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index f15f0368974e..f08878f658d3 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -1540,6 +1540,21 @@ public class DisplayModeDirector { private final Injector mInjector; private final Handler mHandler; + private final IThermalEventListener.Stub mThermalListener = + new IThermalEventListener.Stub() { + @Override + public void notifyThrottling(Temperature temp) { + @Temperature.ThrottlingStatus int currentStatus = temp.getStatus(); + synchronized (mLock) { + if (mThermalStatus != currentStatus) { + mThermalStatus = currentStatus; + } + onBrightnessChangedLocked(); + } + } + }; + private boolean mThermalRegistered; + // Enable light sensor only when mShouldObserveAmbientLowChange is true or // mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate // changeable and low power mode off. After initialization, these states will @@ -1548,9 +1563,17 @@ public class DisplayModeDirector { private boolean mRefreshRateChangeable = false; private boolean mLowPowerModeEnabled = false; + @Nullable + private SparseArray<RefreshRateRange> mLowZoneRefreshRateForThermals; private int mRefreshRateInLowZone; + + @Nullable + private SparseArray<RefreshRateRange> mHighZoneRefreshRateForThermals; private int mRefreshRateInHighZone; + @GuardedBy("mLock") + private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE; + BrightnessObserver(Context context, Handler handler, Injector injector) { mContext = context; mHandler = handler; @@ -1649,6 +1672,8 @@ public class DisplayModeDirector { R.integer.config_defaultRefreshRateInZone) : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); } + mLowZoneRefreshRateForThermals = displayDeviceConfig == null ? null + : displayDeviceConfig.getLowBlockingZoneThermalMap(); mRefreshRateInLowZone = refreshRateInLowZone; } @@ -1668,6 +1693,8 @@ public class DisplayModeDirector { R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(); } + mHighZoneRefreshRateForThermals = displayDeviceConfig == null ? null + : displayDeviceConfig.getHighBlockingZoneThermalMap(); mRefreshRateInHighZone = refreshRateInHighZone; } @@ -2117,6 +2144,15 @@ public class DisplayModeDirector { if (insideLowZone) { refreshRateVote = Vote.forPhysicalRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone); + if (mLowZoneRefreshRateForThermals != null) { + RefreshRateRange range = SkinThermalStatusObserver + .findBestMatchingRefreshRateRange(mThermalStatus, + mLowZoneRefreshRateForThermals); + if (range != null) { + refreshRateVote = + Vote.forPhysicalRefreshRates(range.min, range.max); + } + } refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); } @@ -2126,6 +2162,15 @@ public class DisplayModeDirector { refreshRateVote = Vote.forPhysicalRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone); + if (mHighZoneRefreshRateForThermals != null) { + RefreshRateRange range = SkinThermalStatusObserver + .findBestMatchingRefreshRateRange(mThermalStatus, + mHighZoneRefreshRateForThermals); + if (range != null) { + refreshRateVote = + Vote.forPhysicalRefreshRates(range.min, range.max); + } + } refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching(); } @@ -2184,13 +2229,25 @@ public class DisplayModeDirector { + mRefreshRateChangeable); } + boolean registerForThermals = false; if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) { registerLightSensor(); - + registerForThermals = mLowZoneRefreshRateForThermals != null + || mHighZoneRefreshRateForThermals != null; } else { unregisterSensorListener(); } + + if (registerForThermals && !mThermalRegistered) { + mThermalRegistered = mInjector.registerThermalServiceListener(mThermalListener); + } else if (!registerForThermals && mThermalRegistered) { + mInjector.unregisterThermalServiceListener(mThermalListener); + mThermalRegistered = false; + synchronized (mLock) { + mThermalStatus = Temperature.THROTTLING_NONE; // reset + } + } } private void registerLightSensor() { @@ -2821,6 +2878,7 @@ public class DisplayModeDirector { boolean isDozeState(Display d); boolean registerThermalServiceListener(IThermalEventListener listener); + void unregisterThermalServiceListener(IThermalEventListener listener); boolean supportsFrameRateOverride(); @@ -2922,6 +2980,19 @@ public class DisplayModeDirector { } @Override + public void unregisterThermalServiceListener(IThermalEventListener listener) { + IThermalService thermalService = getThermalService(); + if (thermalService == null) { + Slog.w(TAG, "Could not unregister thermal status. Service not available"); + } + try { + thermalService.unregisterThermalEventListener(listener); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to unregister thermal status listener", e); + } + } + + @Override public boolean supportsFrameRateOverride() { return SurfaceFlingerProperties.enable_frame_rate_override().orElse(true); } diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java index 58e15503be7d..b29cda88802a 100644 --- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java +++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java @@ -64,6 +64,20 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme mHandler = handler; } + @Nullable + public static SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange( + @Temperature.ThrottlingStatus int currentStatus, + SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) { + SurfaceControl.RefreshRateRange foundRange = null; + for (int status = currentStatus; status >= 0; status--) { + foundRange = throttlingMap.get(status); + if (foundRange != null) { + break; + } + } + return foundRange; + } + void observe() { // if failed to register thermal service listener, don't register display listener if (!mInjector.registerThermalServiceListener(this)) { @@ -228,20 +242,6 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme } } - @Nullable - private SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange( - @Temperature.ThrottlingStatus int currentStatus, - SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) { - SurfaceControl.RefreshRateRange foundRange = null; - for (int status = currentStatus; status >= 0; status--) { - foundRange = throttlingMap.get(status); - if (foundRange != null) { - break; - } - } - return foundRange; - } - private void fallbackReportThrottlingIfNeeded(int displayId, @Temperature.ThrottlingStatus int currentStatus) { Vote vote = null; diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java index d238dae634ad..2ede56dcecd9 100644 --- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java +++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java @@ -67,7 +67,7 @@ class GestureMonitorSpyWindow { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setInputWindowInfo(mInputSurface, mWindowHandle); - t.setLayer(mInputSurface, Integer.MAX_VALUE); + t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR); t.setPosition(mInputSurface, 0, 0); t.setCrop(mInputSurface, null /* crop to parent surface */); t.show(mInputSurface); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index b8e9d5dfb3bc..62660c4f3c6d 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -381,6 +381,17 @@ public class InputManagerService extends IInputManager.Stub public static final int SW_CAMERA_LENS_COVER_BIT = 1 << SW_CAMERA_LENS_COVER; public static final int SW_MUTE_DEVICE_BIT = 1 << SW_MUTE_DEVICE; + // The following are layer numbers used for z-ordering the input overlay layers on the display. + // This is used for ordering layers inside {@code DisplayContent#getInputOverlayLayer()}. + // + // The layer where gesture monitors are added. + public static final int INPUT_OVERLAY_LAYER_GESTURE_MONITOR = 1; + // Place the handwriting layer above gesture monitors so that styluses cannot trigger + // system gestures (e.g. navigation bar, edge-back, etc) while there is an active + // handwriting session. + public static final int INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE = 2; + + private final String mVelocityTrackerStrategy; /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */ diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java index 0c889c2765bb..7726f40fa2ae 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java @@ -27,16 +27,13 @@ import android.view.InputWindowHandle; import android.view.SurfaceControl; import android.view.WindowManager; +import com.android.server.input.InputManagerService; + final class HandwritingEventReceiverSurface { public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName(); static final boolean DEBUG = HandwritingModeController.DEBUG; - // Place the layer at the highest layer so stylus cannot trigger gesture monitors - // (e.g. navigation bar, edge-back, etc) while handwriting is ongoing. - // TODO(b/217538817): Specify the ordering in WM by usage. - private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE; - private final InputWindowHandle mWindowHandle; private final InputChannel mClientChannel; private final SurfaceControl mInputSurface; @@ -68,7 +65,7 @@ final class HandwritingEventReceiverSurface { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setInputWindowInfo(mInputSurface, mWindowHandle); - t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER); + t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE); t.setPosition(mInputSurface, 0, 0); t.setCrop(mInputSurface, null /* crop to parent surface */); t.show(mInputSurface); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8e7baf26984a..1ec8b10813cd 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3275,15 +3275,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } else { // If subtype is null, try to find the most applicable one from // getCurrentInputMethodSubtype. + subtypeId = NOT_A_SUBTYPE_ID; newSubtype = getCurrentInputMethodSubtypeLocked(); + if (newSubtype != null) { + for (int i = 0; i < subtypeCount; ++i) { + if (Objects.equals(newSubtype, info.getSubtypeAt(i))) { + subtypeId = i; + break; + } + } + } } - if (newSubtype == null || oldSubtype == null) { - Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype - + ", new subtype = " + newSubtype); - notifyInputMethodSubtypeChangedLocked(userId, info, null); - return; - } - if (!newSubtype.equals(oldSubtype)) { + if (!Objects.equals(newSubtype, oldSubtype)) { setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index d700c6adfebb..8e9c21f5f35f 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -77,7 +77,6 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; - /** * A class that manages a user's synthetic password (SP) ({@link #SyntheticPassword}), along with a * set of SP protectors that are independent ways that the SP is protected. @@ -552,22 +551,48 @@ class SyntheticPasswordManager { } } - private @Nullable IWeaver getWeaverServiceInternal() { - // Try to get the AIDL service first + private @Nullable IWeaver getWeaverAidlService() { + final IWeaver aidlWeaver; try { - IWeaver aidlWeaver = IWeaver.Stub.asInterface( - ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default")); - if (aidlWeaver != null) { - Slog.i(TAG, "Using AIDL weaver service"); - try { - aidlWeaver.asBinder().linkToDeath(new WeaverDiedRecipient(), 0); - } catch (RemoteException e) { - Slog.w(TAG, "Unable to register Weaver death recipient", e); - } - return aidlWeaver; - } + aidlWeaver = + IWeaver.Stub.asInterface( + ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default")); } catch (SecurityException e) { Slog.w(TAG, "Does not have permissions to get AIDL weaver service"); + return null; + } + if (aidlWeaver == null) { + return null; + } + final int aidlVersion; + try { + aidlVersion = aidlWeaver.getInterfaceVersion(); + } catch (RemoteException e) { + Slog.e(TAG, "Cannot get AIDL weaver service version", e); + return null; + } + if (aidlVersion < 2) { + Slog.w(TAG, + "Ignoring AIDL weaver service v" + + aidlVersion + + " because only v2 and later are supported"); + return null; + } + Slog.i(TAG, "Found AIDL weaver service v" + aidlVersion); + return aidlWeaver; + } + + private @Nullable IWeaver getWeaverServiceInternal() { + // Try to get the AIDL service first + IWeaver aidlWeaver = getWeaverAidlService(); + if (aidlWeaver != null) { + Slog.i(TAG, "Using AIDL weaver service"); + try { + aidlWeaver.asBinder().linkToDeath(new WeaverDiedRecipient(), 0); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to register Weaver death recipient", e); + } + return aidlWeaver; } // If the AIDL service can't be found, look for the HIDL service diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 37bcfbb03899..5b870692e348 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -313,7 +313,7 @@ class MediaRouter2ServiceImpl { if (linkedItemLandingComponent != null) { int callingUid = Binder.getCallingUid(); MediaServerUtils.enforcePackageName( - linkedItemLandingComponent.getPackageName(), callingUid); + mContext, linkedItemLandingComponent.getPackageName(), callingUid); if (!MediaServerUtils.isValidActivityComponentName( mContext, linkedItemLandingComponent, diff --git a/services/core/java/com/android/server/media/MediaServerUtils.java b/services/core/java/com/android/server/media/MediaServerUtils.java index 60592feb867d..6a954d66d18d 100644 --- a/services/core/java/com/android/server/media/MediaServerUtils.java +++ b/services/core/java/com/android/server/media/MediaServerUtils.java @@ -31,6 +31,7 @@ import android.text.TextUtils; import com.android.server.LocalServices; import java.io.PrintWriter; +import java.util.Arrays; import java.util.List; /** Util class for media server. */ @@ -63,7 +64,8 @@ import java.util.List; * @throws IllegalArgumentException If the given {@code packageName} does not correspond to the * given {@code uid}, and {@code uid} is not the root uid, or the shell uid. */ - public static void enforcePackageName(String packageName, int uid) { + public static void enforcePackageName( + @NonNull Context context, @NonNull String packageName, int uid) { if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) { return; } @@ -76,12 +78,16 @@ import java.util.List; packageManagerInternal.getPackageUid( packageName, 0 /* flags */, UserHandle.getUserId(uid)); if (!UserHandle.isSameApp(uid, actualUid)) { + String[] uidPackages = context.getPackageManager().getPackagesForUid(uid); throw new IllegalArgumentException( "packageName does not belong to the calling uid; " + "pkg=" + packageName + ", uid=" - + uid); + + uid + + " (" + + Arrays.toString(uidPackages) + + ")"); } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index f2242bf48dcd..f4c95185210a 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -542,7 +542,7 @@ public class MediaSessionService extends SystemService implements Monitor { int callingPid, int callingUid, String callingPackage, String reason) { final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(callingPackage, callingUid); + MediaServerUtils.enforcePackageName(mContext, callingPackage, callingUid); if (targetUid != callingUid) { boolean canAllowWhileInUse = mActivityManagerLocal .canAllowWhileInUsePermissionInFgs(callingPid, callingUid, callingPackage); @@ -1187,7 +1187,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName); if (cb == null) { throw new IllegalArgumentException("Controller callback cannot be null"); @@ -1239,7 +1239,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int userId = userHandle.getIdentifier(); final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); enforceMediaPermissions(packageName, pid, uid, userId); MediaSessionRecordImpl record; @@ -1270,7 +1270,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int userId = userHandle.getIdentifier(); final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); enforceMediaPermissions(packageName, pid, uid, userId); MediaSessionRecordImpl record; @@ -1596,7 +1596,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int userId = userHandle.getIdentifier(); final long token = Binder.clearCallingIdentity(); try { - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); enforceMediaPermissions(packageName, pid, uid, userId); synchronized (mLock) { @@ -2110,7 +2110,7 @@ public class MediaSessionService extends SystemService implements Monitor { // If they gave us a component name verify they own the // package packageName = componentName.getPackageName(); - MediaServerUtils.enforcePackageName(packageName, uid); + MediaServerUtils.enforcePackageName(mContext, packageName, uid); } // Check that they can make calls on behalf of the user and get the final user id int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 009cc3b57594..6f0a4b48c09e 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1928,6 +1928,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onUserRemoved(userId); mAssistants.onUserRemoved(userId); mHistoryManager.onUserRemoved(userId); + mPreferencesHelper.syncChannelsBypassingDnd(); handleSavePolicyFile(); } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); @@ -2379,6 +2380,7 @@ public class NotificationManagerService extends SystemService { }); mPermissionHelper = permissionHelper; mNotificationChannelLogger = channelLogger; + mUserProfiles.updateCache(getContext()); mPreferencesHelper = new PreferencesHelper(getContext(), mPackageManagerClient, mRankingHandler, @@ -2387,6 +2389,7 @@ public class NotificationManagerService extends SystemService { mPermissionManager, mNotificationChannelLogger, mAppOps, + mUserProfiles, new SysUiStatsEvent.BuilderFactory(), mShowReviewPermissionsNotification); mRankingHelper = new RankingHelper(getContext(), @@ -2442,8 +2445,6 @@ public class NotificationManagerService extends SystemService { mZenModeHelper.initZenMode(); mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter(); - mUserProfiles.updateCache(getContext()); - if (mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { telephonyManager.listen(new PhoneStateListener() { @Override diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 3f799dc08a3f..0e37f101ce70 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -38,7 +38,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationChannel; @@ -53,7 +52,6 @@ import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.metrics.LogMaker; import android.net.Uri; -import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; @@ -114,7 +112,6 @@ public class PreferencesHelper implements RankingConfig { private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4; @VisibleForTesting static final int UNKNOWN_UID = UserHandle.USER_NULL; - private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":"; @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000; @@ -196,6 +193,7 @@ public class PreferencesHelper implements RankingConfig { private final PermissionManager mPermissionManager; private final NotificationChannelLogger mNotificationChannelLogger; private final AppOpsManager mAppOps; + private final ManagedServices.UserProfiles mUserProfiles; private SparseBooleanArray mBadgingEnabled; private SparseBooleanArray mBubblesEnabled; @@ -204,14 +202,12 @@ public class PreferencesHelper implements RankingConfig { private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING; private boolean mCurrentUserHasChannelsBypassingDnd; private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; - private boolean mShowReviewPermissionsNotification; - - private boolean mAllowInvalidShortcuts = false; + private final boolean mShowReviewPermissionsNotification; public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager, NotificationChannelLogger notificationChannelLogger, - AppOpsManager appOpsManager, + AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles, SysUiStatsEvent.BuilderFactory statsEventBuilderFactory, boolean showReviewPermissionsNotification) { mContext = context; @@ -222,6 +218,7 @@ public class PreferencesHelper implements RankingConfig { mPm = pm; mNotificationChannelLogger = notificationChannelLogger; mAppOps = appOpsManager; + mUserProfiles = userProfiles; mStatsEventBuilderFactory = statsEventBuilderFactory; mShowReviewPermissionsNotification = showReviewPermissionsNotification; @@ -435,7 +432,7 @@ public class PreferencesHelper implements RankingConfig { channel.getConversationId() != null && channel.getConversationId().contains( PLACEHOLDER_CONVERSATION_ID); - return mAllowInvalidShortcuts || (!mAllowInvalidShortcuts && !isInvalidShortcutChannel); + return !isInvalidShortcutChannel; } private boolean isDeletionOk(NotificationChannel nc) { @@ -1790,8 +1787,9 @@ public class PreferencesHelper implements RankingConfig { * Syncs {@link #mCurrentUserHasChannelsBypassingDnd} with the current user's notification * policy before updating. Must be called: * <ul> - * <li>On system init, after channels and DND configurations are loaded.</li> - * <li>When the current user changes, after the corresponding DND config is loaded.</li> + * <li>On system init, after channels and DND configurations are loaded. + * <li>When the current user is switched, after the corresponding DND config is loaded. + * <li>If users are removed (the removed user could've been a profile of the current one). * </ul> */ void syncChannelsBypassingDnd() { @@ -1805,20 +1803,19 @@ public class PreferencesHelper implements RankingConfig { /** * Updates the user's NotificationPolicy based on whether the current userId has channels * bypassing DND. It should be called whenever a channel is created, updated, or deleted, or - * when the current user is switched. + * when the current user (or its profiles) change. */ private void updateCurrentUserHasChannelsBypassingDnd(int callingUid, boolean fromSystemOrSystemUi) { ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>(); - final int currentUserId = getCurrentUser(); + final IntArray currentUserIds = mUserProfiles.getCurrentProfileIds(); synchronized (mPackagePreferences) { final int numPackagePreferences = mPackagePreferences.size(); for (int i = 0; i < numPackagePreferences; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); - // Package isn't associated with the current userId - if (currentUserId != UserHandle.getUserId(r.uid)) { - continue; + if (!currentUserIds.contains(UserHandle.getUserId(r.uid))) { + continue; // Package isn't associated with any profile of the current userId. } for (NotificationChannel channel : r.channels.values()) { @@ -1842,13 +1839,6 @@ public class PreferencesHelper implements RankingConfig { } } - private int getCurrentUser() { - final long identity = Binder.clearCallingIdentity(); - int currentUserId = ActivityManager.getCurrentUser(); - Binder.restoreCallingIdentity(identity); - return currentUserId; - } - private boolean channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel) { // Channel is in a group that's blocked if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) { diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index c0bc6d14cd04..1908e4dff234 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -5,4 +5,11 @@ flag { namespace: "systemui" description: "This flag controls removing expired notification bitmaps" bug: "290381858" -}
\ No newline at end of file +} + +flag { + name: "polite_notifications" + namespace: "systemui" + description: "This flag controls the polite notification feature" + bug: "270456865" +} diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index b5ec1366fec6..66a170397b84 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -459,8 +459,8 @@ final class DeletePackageHelper { // Do not uninstall the APK if an app should be cached boolean keepUninstalledPackage = mPm.shouldKeepUninstalledPackageLPr(packageName); - if (ps.isAnyInstalled( - mUserManagerInternal.getUserIds()) || keepUninstalledPackage) { + if (ps.isInstalledOrHasDataOnAnyOtherUser( + mUserManagerInternal.getUserIds(), userId) || keepUninstalledPackage) { // Other users still have this package installed, so all // we need to do is clear this user's data and save that // it is uninstalled. @@ -555,6 +555,7 @@ final class DeletePackageHelper { if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { mAppDataHelper.destroyAppDataLIF(pkg, nextUserId, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); + ps.setCeDataInode(-1, nextUserId); } mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId()); preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(), @@ -615,7 +616,9 @@ final class DeletePackageHelper { Slog.d(TAG, "Marking package:" + ps.getPackageName() + " uninstalled for user:" + nextUserId); } - ps.setUserState(nextUserId, 0, COMPONENT_ENABLED_STATE_DEFAULT, + ps.setUserState(nextUserId, + ps.getCeDataInode(nextUserId), + COMPONENT_ENABLED_STATE_DEFAULT, false /*installed*/, true /*stopped*/, true /*notLaunched*/, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6c0aeecbf68b..f0bbd3557f85 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2892,13 +2892,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService private void notifyPackageUseInternal(String packageName, int reason) { long time = System.currentTimeMillis(); - synchronized (mLock) { - final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName); - if (pkgSetting == null) { - return; - } - pkgSetting.getPkgState().setLastPackageUsageTimeInMills(reason, time); - } + this.commitPackageStateMutation(null, mutator -> { + final PackageStateWrite state = mutator.forPackage(packageName); + state.setLastPackageUsageTime(reason, time); + }); } /*package*/ DexManager getDexManager() { @@ -5182,9 +5179,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService public int getUserMinAspectRatio(@NonNull String packageName, int userId) { final Computer snapshot = snapshotComputer(); final int callingUid = Binder.getCallingUid(); - snapshot.enforceCrossUserPermission( - callingUid, userId, false /* requireFullPermission */, - false /* checkShell */, "getUserMinAspectRatio"); final PackageStateInternal packageState = snapshot .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId); return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET @@ -6681,9 +6675,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override public void notifyPackageUse(String packageName, int reason) { - synchronized (mLock) { - PackageManagerService.this.notifyPackageUseInternal(packageName, reason); - } + PackageManagerService.this.notifyPackageUseInternal(packageName, reason); } @Nullable diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 14693a67f2a5..7cac3e1b9842 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -809,9 +809,16 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return changed; } - boolean isAnyInstalled(int[] users) { - for (int user: users) { - if (readUserState(user).isInstalled()) { + boolean isInstalledOrHasDataOnAnyOtherUser(int[] allUsers, int currentUser) { + for (int user: allUsers) { + if (user == currentUser) { + continue; + } + final PackageUserStateInternal userState = readUserState(user); + if (userState.isInstalled()) { + return true; + } + if (userState.getCeDataInode() > 0) { return true; } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index b6da4627c45a..307867c4e272 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -5186,9 +5186,12 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pw.print(" instant="); pw.print(userState.isInstantApp()); pw.print(" virtual="); - pw.println(userState.isVirtualPreload()); + pw.print(userState.isVirtualPreload()); pw.print(" quarantined="); pw.print(userState.isQuarantined()); + + // Dump install state with additional indentation on their own lines. + pw.println(); pw.print(" installReason="); pw.println(userState.getInstallReason()); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index b01a89e672be..789719527c8d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -72,6 +72,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.TriFunction; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; @@ -93,7 +94,6 @@ import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiFunction; /** * Manages all permissions and handles permissions related tasks. @@ -233,11 +233,11 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (checkPermissionDelegate == null) { - return mPermissionManagerServiceImpl.checkPermission( - packageName, permissionName, userId); + return mPermissionManagerServiceImpl.checkPermission(packageName, permissionName, + deviceId, userId); } - return checkPermissionDelegate.checkPermission(packageName, permissionName, userId, - mPermissionManagerServiceImpl::checkPermission); + return checkPermissionDelegate.checkPermission(packageName, permissionName, + deviceId, userId, mPermissionManagerServiceImpl::checkPermission); } @Override @@ -254,10 +254,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { } if (checkPermissionDelegate == null) { - return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName); + return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName, deviceId); } return checkPermissionDelegate.checkUidPermission(uid, permissionName, - mPermissionManagerServiceImpl::checkUidPermission); + deviceId, mPermissionManagerServiceImpl::checkUidPermission); } @Override @@ -511,14 +511,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { public int getPermissionFlags(String packageName, String permissionName, int deviceId, int userId) { return mPermissionManagerServiceImpl - .getPermissionFlags(packageName, permissionName, userId); + .getPermissionFlags(packageName, permissionName, deviceId, userId); } @Override public void updatePermissionFlags(String packageName, String permissionName, int flagMask, int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) { mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask, - flagValues, checkAdjustPolicyFlagPermission, userId); + flagValues, checkAdjustPolicyFlagPermission, deviceId, userId); } @Override @@ -560,14 +560,15 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void grantRuntimePermission(String packageName, String permissionName, int deviceId, int userId) { - mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId); + mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, + deviceId, userId); } @Override public void revokeRuntimePermission(String packageName, String permissionName, int deviceId, int userId, String reason) { mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName, - userId, reason); + deviceId, userId, reason); } @Override @@ -580,14 +581,14 @@ public class PermissionManagerService extends IPermissionManager.Stub { public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName, int deviceId, int userId) { return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName, - permissionName, userId); + permissionName, deviceId, userId); } @Override public boolean isPermissionRevokedByPolicy(String packageName, String permissionName, int deviceId, int userId) { - return mPermissionManagerServiceImpl - .isPermissionRevokedByPolicy(packageName, permissionName, userId); + return mPermissionManagerServiceImpl.isPermissionRevokedByPolicy(packageName, + permissionName, deviceId, userId); } @Override @@ -868,6 +869,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { * * @param packageName the name of the package to be checked * @param permissionName the name of the permission to be checked + * @param deviceId The device ID * @param userId the user ID * @param superImpl the original implementation that can be delegated to * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has @@ -876,20 +878,21 @@ public class PermissionManagerService extends IPermissionManager.Stub { * @see android.content.pm.PackageManager#checkPermission(String, String) */ int checkPermission(@NonNull String packageName, @NonNull String permissionName, - @UserIdInt int userId, - @NonNull TriFunction<String, String, Integer, Integer> superImpl); + int deviceId, @UserIdInt int userId, + @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl); /** * Check whether the given UID has been granted the specified permission. * * @param uid the UID to be checked * @param permissionName the name of the permission to be checked + * @param deviceId The device ID * @param superImpl the original implementation that can be delegated to * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise */ - int checkUidPermission(int uid, @NonNull String permissionName, - BiFunction<Integer, String, Integer> superImpl); + int checkUidPermission(int uid, @NonNull String permissionName, int deviceId, + TriFunction<Integer, String, Integer, Integer> superImpl); /** * @return list of delegated permissions @@ -918,31 +921,32 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public int checkPermission(@NonNull String packageName, @NonNull String permissionName, - int userId, @NonNull TriFunction<String, String, Integer, Integer> superImpl) { + int deviceId, int userId, + @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl) { if (mDelegatedPackageName.equals(packageName) && isDelegatedPermission(permissionName)) { final long identity = Binder.clearCallingIdentity(); try { - return superImpl.apply("com.android.shell", permissionName, userId); + return superImpl.apply("com.android.shell", permissionName, deviceId, userId); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(packageName, permissionName, userId); + return superImpl.apply(packageName, permissionName, deviceId, userId); } @Override - public int checkUidPermission(int uid, @NonNull String permissionName, - @NonNull BiFunction<Integer, String, Integer> superImpl) { + public int checkUidPermission(int uid, @NonNull String permissionName, int deviceId, + @NonNull TriFunction<Integer, String, Integer, Integer> superImpl) { if (uid == mDelegatedUid && isDelegatedPermission(permissionName)) { final long identity = Binder.clearCallingIdentity(); try { - return superImpl.apply(Process.SHELL_UID, permissionName); + return superImpl.apply(Process.SHELL_UID, permissionName, deviceId); } finally { Binder.restoreCallingIdentity(identity); } } - return superImpl.apply(uid, permissionName); + return superImpl.apply(uid, permissionName, deviceId); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 4353c5787d4b..6764e087ff04 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -681,7 +681,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public int getPermissionFlags(String packageName, String permName, int userId) { + public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) { final int callingUid = Binder.getCallingUid(); return getPermissionFlagsInternal(packageName, permName, callingUid, userId); } @@ -724,7 +724,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) { final int callingUid = Binder.getCallingUid(); boolean overridePolicy = false; @@ -908,8 +908,12 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } + private int checkPermission(String pkgName, String permName, int userId) { + return checkPermission(pkgName, permName, Context.DEVICE_ID_DEFAULT, userId); + } + @Override - public int checkPermission(String pkgName, String permName, int userId) { + public int checkPermission(String pkgName, String permName, int deviceId, int userId) { if (!mUserManagerInt.exists(userId)) { return PackageManager.PERMISSION_DENIED; } @@ -975,8 +979,12 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt return true; } + private int checkUidPermission(int uid, String permName) { + return checkUidPermission(uid, permName, Context.DEVICE_ID_DEFAULT); + } + @Override - public int checkUidPermission(int uid, String permName) { + public int checkUidPermission(int uid, String permName, int deviceId) { final int userId = UserHandle.getUserId(uid); if (!mUserManagerInt.exists(userId)) { return PackageManager.PERMISSION_DENIED; @@ -1295,7 +1303,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void grantRuntimePermission(String packageName, String permName, final int userId) { + public void grantRuntimePermission(String packageName, String permName, int deviceId, + int userId) { final int callingUid = Binder.getCallingUid(); final boolean overridePolicy = checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) @@ -1468,11 +1477,11 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void revokeRuntimePermission(String packageName, String permName, int userId, - String reason) { + public void revokeRuntimePermission(String packageName, String permName, int deviceId, + int userId, String reason) { final int callingUid = Binder.getCallingUid(); final boolean overridePolicy = - checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY) + checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY, deviceId) == PackageManager.PERMISSION_GRANTED; revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId, @@ -1859,7 +1868,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - @UserIdInt int userId) { + int deviceId, @UserIdInt int userId) { final int callingUid = Binder.getCallingUid(); if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( @@ -1922,7 +1931,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + int userId) { if (UserHandle.getCallingUserId() != userId) { mContext.enforceCallingPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, @@ -2059,8 +2069,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt continue; } boolean isSystemOrPolicyFixed = (getPermissionFlags(newPackage.getPackageName(), - permInfo.name, userId) & (FLAG_PERMISSION_SYSTEM_FIXED - | FLAG_PERMISSION_POLICY_FIXED)) != 0; + permInfo.name, Context.DEVICE_ID_DEFAULT, userId) & ( + FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED)) != 0; if (isSystemOrPolicyFixed) { continue; } @@ -2226,7 +2236,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt for (final int userId : userIds) { final int permissionState = checkPermission(packageName, permName, userId); - final int flags = getPermissionFlags(packageName, permName, userId); + final int flags = getPermissionFlags(packageName, permName, + Context.DEVICE_ID_DEFAULT, userId); final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED | FLAG_PERMISSION_GRANTED_BY_DEFAULT @@ -5122,8 +5133,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt @NonNull @Override - public Set<String> getGrantedPermissions(@NonNull String packageName, - @UserIdInt int userId) { + public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) { Objects.requireNonNull(packageName, "packageName"); Preconditions.checkArgumentNonNegative(userId, "userId"); return getGrantedPermissionsInternal(packageName, userId); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 128f847715ab..2d824aa1ba13 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -25,7 +25,6 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.permission.IOnPermissionsChangeListener; -import android.permission.PermissionManager; import android.permission.PermissionManagerInternal; import com.android.server.pm.pkg.AndroidPackage; @@ -137,14 +136,16 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte void removePermission(String permName); /** - * Gets the state flags associated with a permission. + * Gets the permission state flags associated with a permission. * * @param packageName the package name for which to get the flags * @param permName the permission for which to get the flags + * @param deviceId The device for which to get the flags * @param userId the user for which to get permission flags * @return the permission flags */ - int getPermissionFlags(String packageName, String permName, int userId); + int getPermissionFlags(String packageName, String permName, int deviceId, + @UserIdInt int userId); /** * Updates the flags associated with a permission by replacing the flags in the specified mask @@ -154,10 +155,11 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * @param permName The permission for which to update the flags * @param flagMask The flags which to replace * @param flagValues The flags with which to replace + * @param deviceId The device for which to update the permission flags * @param userId The user for which to update the permission flags */ - void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int userId); + void updatePermissionFlags(String packageName, String permName, int flagMask, int flagValues, + boolean checkAdjustPolicyFlagPermission, int deviceId, @UserIdInt int userId); /** * Update the permission flags for all packages and runtime permissions of a user in order @@ -291,11 +293,13 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName the package to which to grant the permission * @param permName the permission name to grant + * @param deviceId the device for which to grant the permission * @param userId the user for which to grant the permission * - * @see #revokeRuntimePermission(String, String, android.os.UserHandle, String) + * @see #revokeRuntimePermission(String, String, int, int, String) */ - void grantRuntimePermission(String packageName, String permName, int userId); + void grantRuntimePermission(String packageName, String permName, int deviceId, + @UserIdInt int userId); /** * Revoke a runtime permission that was previously granted by @@ -310,13 +314,14 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName the package from which to revoke the permission * @param permName the permission name to revoke + * @param deviceId the device for which to revoke the permission * @param userId the user for which to revoke the permission * @param reason the reason for the revoke, or {@code null} for unspecified * - * @see #grantRuntimePermission(String, String, android.os.UserHandle) + * @see #grantRuntimePermission(String, String, int, int) */ - void revokeRuntimePermission(String packageName, String permName, int userId, - String reason); + void revokeRuntimePermission(String packageName, String permName, int deviceId, + @UserIdInt int userId, String reason); /** * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE @@ -333,24 +338,29 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * does not clearly communicate to the user what would be the benefit from grating this * permission. * + * @param packageName the package name * @param permName a permission your app wants to request + * @param deviceId the device for which to check the permission + * @param userId the user for which to check the permission * @return whether you can show permission rationale UI */ boolean shouldShowRequestPermissionRationale(String packageName, String permName, - @UserIdInt int userId); + int deviceId, @UserIdInt int userId); /** - * Checks whether a particular permissions has been revoked for a package by policy. Typically + * Checks whether a particular permission has been revoked for a package by policy. Typically, * the device owner or the profile owner may apply such a policy. The user cannot grant policy * revoked permissions, hence the only way for an app to get such a permission is by a policy * change. * * @param packageName the name of the package you are checking against * @param permName the name of the permission you are checking for - * + * @param deviceId the device for which you are checking the permission + * @param userId the device for which you are checking the permission * @return whether the permission is restricted by policy */ - boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId); + boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + @UserIdInt int userId); /** * Get set of permissions that have been split into more granular or dependent permissions. @@ -373,14 +383,25 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte List<SplitPermissionInfoParcelable> getSplitPermissions(); /** - * TODO:theianchen add doc describing this is the old checkPermissionImpl + * Check whether a permission is granted or not to a package. + * + * @param pkgName package name + * @param permName permission name + * @param deviceId device ID + * @param userId user ID + * @return permission result {@link PackageManager.PermissionResult} */ - int checkPermission(String pkgName, String permName, int userId); + int checkPermission(String pkgName, String permName, int deviceId, @UserIdInt int userId); /** - * TODO:theianchen add doc describing this is the old checkUidPermissionImpl + * Check whether a permission is granted or not to an UID. + * + * @param uid UID + * @param permName permission name + * @param deviceId device ID + * @return permission result {@link PackageManager.PermissionResult} */ - int checkUidPermission(int uid, String permName); + int checkUidPermission(int uid, String permName, int deviceId); /** * Get all the package names requesting app op permissions. @@ -400,15 +421,11 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte @UserIdInt int userId); /** - * Reset the runtime permission state changes for a package. + * Reset the runtime permission state changes for a package for all devices. * * TODO(zhanghai): Turn this into package change callback? - * - * @param pkg the package - * @param userId the user ID */ - void resetRuntimePermissions(@NonNull AndroidPackage pkg, - @UserIdInt int userId); + void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId); /** * Reset the runtime permission state changes for all packages in a user. @@ -449,8 +466,8 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte /** * Get all the permissions granted to a package. * - * @param packageName the name of the package - * @param userId the user ID + * @param packageName package name + * @param userId user ID * @return the names of the granted permissions */ @NonNull diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java index 7f98e2163178..dacb8c6890a0 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java @@ -120,21 +120,21 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public int getPermissionFlags(String packageName, String permName, int userId) { + public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) { Log.i(LOG_TAG, "getPermissionFlags(packageName = " + packageName + ", permName = " - + permName + ", userId = " + userId + ")"); - return mService.getPermissionFlags(packageName, permName, userId); + + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); + return mService.getPermissionFlags(packageName, permName, deviceId, userId); } @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) { Log.i(LOG_TAG, "updatePermissionFlags(packageName = " + packageName + ", permName = " + permName + ", flagMask = " + flagMask + ", flagValues = " + flagValues + ", checkAdjustPolicyFlagPermission = " + checkAdjustPolicyFlagPermission - + ", userId = " + userId + ")"); + + ", deviceId = " + deviceId + ", userId = " + userId + ")"); mService.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, userId); + checkAdjustPolicyFlagPermission, deviceId, userId); } @Override @@ -182,18 +182,20 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public void grantRuntimePermission(String packageName, String permName, int userId) { + public void grantRuntimePermission(String packageName, String permName, int deviceId, + int userId) { Log.i(LOG_TAG, "grantRuntimePermission(packageName = " + packageName + ", permName = " - + permName + ", userId = " + userId + ")"); - mService.grantRuntimePermission(packageName, permName, userId); + + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); + mService.grantRuntimePermission(packageName, permName, deviceId, userId); } @Override - public void revokeRuntimePermission(String packageName, String permName, int userId, - String reason) { + public void revokeRuntimePermission(String packageName, String permName, int deviceId, + int userId, String reason) { Log.i(LOG_TAG, "revokeRuntimePermission(packageName = " + packageName + ", permName = " - + permName + ", userId = " + userId + ", reason = " + reason + ")"); - mService.revokeRuntimePermission(packageName, permName, userId, reason); + + permName + ", deviceId = " + deviceId + ", userId = " + userId + + ", reason = " + reason + ")"); + mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); } @Override @@ -205,17 +207,20 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int userId) { + int deviceId, int userId) { Log.i(LOG_TAG, "shouldShowRequestPermissionRationale(packageName = " + packageName - + ", permName = " + permName + ", userId = " + userId + ")"); - return mService.shouldShowRequestPermissionRationale(packageName, permName, userId); + + ", permName = " + permName + ", deviceId = " + deviceId + + ", userId = " + userId + ")"); + return mService.shouldShowRequestPermissionRationale(packageName, permName, deviceId, + userId); } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + int userId) { Log.i(LOG_TAG, "isPermissionRevokedByPolicy(packageName = " + packageName + ", permName = " - + permName + ", userId = " + userId + ")"); - return mService.isPermissionRevokedByPolicy(packageName, permName, userId); + + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")"); + return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId); } @Override @@ -225,16 +230,17 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag } @Override - public int checkPermission(String pkgName, String permName, int userId) { + public int checkPermission(String pkgName, String permName, int deviceId, int userId) { Log.i(LOG_TAG, "checkPermission(pkgName = " + pkgName + ", permName = " + permName - + ", userId = " + userId + ")"); - return mService.checkPermission(pkgName, permName, userId); + + ", deviceId = " + deviceId + ", userId = " + userId + ")"); + return mService.checkPermission(pkgName, permName, deviceId, userId); } @Override - public int checkUidPermission(int uid, String permName) { - Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName + ")"); - return mService.checkUidPermission(uid, permName); + public int checkUidPermission(int uid, String permName, int deviceId) { + Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName + + ", deviceId = " + deviceId + ")"); + return mService.checkUidPermission(uid, permName, deviceId); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java index d4c6d42deeaa..35d165b9b54a 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java @@ -153,9 +153,10 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int getPermissionFlags(String packageName, String permName, int userId) { - int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, userId); - int newVal = mNewImplementation.getPermissionFlags(packageName, permName, userId); + public int getPermissionFlags(String packageName, String permName, int deviceId, + @UserIdInt int userId) { + int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, deviceId, userId); + int newVal = mNewImplementation.getPermissionFlags(packageName, permName, deviceId, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("getPermissionFlags"); @@ -165,11 +166,12 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, + @UserIdInt int userId) { mOldImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, userId); + checkAdjustPolicyFlagPermission, deviceId, userId); mNewImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, userId); + checkAdjustPolicyFlagPermission, deviceId, userId); } @Override @@ -234,16 +236,17 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public void grantRuntimePermission(String packageName, String permName, int userId) { - mOldImplementation.grantRuntimePermission(packageName, permName, userId); - mNewImplementation.grantRuntimePermission(packageName, permName, userId); + public void grantRuntimePermission(String packageName, String permName, int deviceId, + @UserIdInt int userId) { + mOldImplementation.grantRuntimePermission(packageName, permName, deviceId, userId); + mNewImplementation.grantRuntimePermission(packageName, permName, deviceId, userId); } @Override - public void revokeRuntimePermission(String packageName, String permName, int userId, - String reason) { - mOldImplementation.grantRuntimePermission(packageName, permName, userId); - mNewImplementation.grantRuntimePermission(packageName, permName, userId); + public void revokeRuntimePermission(String packageName, String permName, int deviceId, + @UserIdInt int userId, String reason) { + mOldImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); + mNewImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); } @Override @@ -255,11 +258,11 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int userId) { - boolean oldVal = mOldImplementation - .shouldShowRequestPermissionRationale(packageName, permName, userId); - boolean newVal = mNewImplementation - .shouldShowRequestPermissionRationale(packageName, permName, userId); + int deviceId, @UserIdInt int userId) { + boolean oldVal = mOldImplementation.shouldShowRequestPermissionRationale(packageName, + permName, deviceId, userId); + boolean newVal = mNewImplementation.shouldShowRequestPermissionRationale(packageName, + permName, deviceId, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("shouldShowRequestPermissionRationale"); @@ -268,11 +271,12 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { - boolean oldVal = mOldImplementation - .isPermissionRevokedByPolicy(packageName, permName, userId); + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + @UserIdInt int userId) { + boolean oldVal = mOldImplementation.isPermissionRevokedByPolicy(packageName, permName, + deviceId, userId); boolean newVal = mNewImplementation.isPermissionRevokedByPolicy(packageName, permName, - userId); + deviceId, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("isPermissionRevokedByPolicy"); @@ -292,9 +296,9 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int checkPermission(String pkgName, String permName, int userId) { - int oldVal = mOldImplementation.checkPermission(pkgName, permName, userId); - int newVal = mNewImplementation.checkPermission(pkgName, permName, userId); + public int checkPermission(String pkgName, String permName, int deviceId, int userId) { + int oldVal = mOldImplementation.checkPermission(pkgName, permName, deviceId, userId); + int newVal = mNewImplementation.checkPermission(pkgName, permName, deviceId, userId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("checkPermission"); @@ -303,9 +307,9 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer } @Override - public int checkUidPermission(int uid, String permName) { - int oldVal = mOldImplementation.checkUidPermission(uid, permName); - int newVal = mNewImplementation.checkUidPermission(uid, permName); + public int checkUidPermission(int uid, String permName, int deviceId) { + int oldVal = mOldImplementation.checkUidPermission(uid, permName, deviceId); + int newVal = mNewImplementation.checkUidPermission(uid, permName, deviceId); if (!Objects.equals(oldVal, newVal)) { signalImplDifference("checkUidPermission"); @@ -372,7 +376,7 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer @NonNull @Override - public Set<String> getGrantedPermissions(@NonNull String packageName, int userId) { + public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) { Set<String> oldVal = mOldImplementation.getGrantedPermissions(packageName, userId); Set<String> newVal = mNewImplementation.getGrantedPermissions(packageName, userId); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java index 4e72fae99c9c..cbeede0f425c 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java @@ -158,10 +158,10 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public int getPermissionFlags(String packageName, String permName, int userId) { + public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionFlags"); try { - return mService.getPermissionFlags(packageName, permName, userId); + return mService.getPermissionFlags(packageName, permName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -169,12 +169,12 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag @Override public void updatePermissionFlags(String packageName, String permName, int flagMask, - int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) { + int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#updatePermissionFlags"); try { mService.updatePermissionFlags(packageName, permName, flagMask, flagValues, - checkAdjustPolicyFlagPermission, userId); + checkAdjustPolicyFlagPermission, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -253,23 +253,24 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public void grantRuntimePermission(String packageName, String permName, int userId) { + public void grantRuntimePermission(String packageName, String permName, int deviceId, + int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#grantRuntimePermission"); try { - mService.grantRuntimePermission(packageName, permName, userId); + mService.grantRuntimePermission(packageName, permName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public void revokeRuntimePermission(String packageName, String permName, int userId, - String reason) { + public void revokeRuntimePermission(String packageName, String permName, int deviceId, + int userId, String reason) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#revokeRuntimePermission"); try { - mService.revokeRuntimePermission(packageName, permName, userId, reason); + mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason); } finally { Trace.traceEnd(TRACE_TAG); } @@ -288,22 +289,24 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag @Override public boolean shouldShowRequestPermissionRationale(String packageName, String permName, - int userId) { + int deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#shouldShowRequestPermissionRationale"); try { - return mService.shouldShowRequestPermissionRationale(packageName, permName, userId); + return mService.shouldShowRequestPermissionRationale( + packageName, permName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) { + public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId, + int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#isPermissionRevokedByPolicy"); try { - return mService.isPermissionRevokedByPolicy(packageName, permName, userId); + return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } @@ -321,20 +324,20 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag } @Override - public int checkPermission(String pkgName, String permName, int userId) { + public int checkPermission(String pkgName, String permName, int deviceId, int userId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkPermission"); try { - return mService.checkPermission(pkgName, permName, userId); + return mService.checkPermission(pkgName, permName, deviceId, userId); } finally { Trace.traceEnd(TRACE_TAG); } } @Override - public int checkUidPermission(int uid, String permName) { + public int checkUidPermission(int uid, String permName, int deviceId) { Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkUidPermission"); try { - return mService.checkUidPermission(uid, permName); + return mService.checkUidPermission(uid, permName, deviceId); } finally { Trace.traceEnd(TRACE_TAG); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2f68021bf356..b3aa09b8f17b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -29,6 +29,7 @@ import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; +import static android.os.IInputConstants.INVALID_INPUT_DEVICE_ID; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -338,6 +339,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS static final int SHORT_PRESS_PRIMARY_NOTHING = 0; static final int SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS = 1; + static final int SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY = 2; // Must match: config_longPressOnStemPrimaryBehavior in config.xml // The config value can be overridden using Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS @@ -609,6 +611,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // What we do when the user double-taps on home private int mDoubleTapOnHomeBehavior; + // Must match config_primaryShortPressTargetActivity in config.xml + ComponentName mPrimaryShortPressTargetActivity; + // Whether to lock the device after the next dreaming transition has finished. private boolean mLockAfterDreamingTransitionFinished; @@ -1374,7 +1379,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyHandled = true; performHapticFeedback(HapticFeedbackConstants.ASSISTANT_BUTTON, false, "Power - Long Press - Go To Assistant"); - final int powerKeyDeviceId = Integer.MIN_VALUE; + final int powerKeyDeviceId = INVALID_INPUT_DEVICE_ID; launchAssistAction(null, powerKeyDeviceId, eventTime, AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS); break; @@ -1461,23 +1466,59 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private void stemPrimarySinglePressAction(int behavior) { + if (DEBUG_INPUT) { + Slog.d(TAG, "stemPrimarySinglePressAction: behavior=" + behavior); + } + if (behavior == SHORT_PRESS_PRIMARY_NOTHING) return; + + final boolean keyguardActive = mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); + if (keyguardActive) { + // If keyguarded then notify the keyguard. + mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY); + return; + } switch (behavior) { - case SHORT_PRESS_PRIMARY_NOTHING: - break; case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS: if (DEBUG_INPUT) { Slog.d(TAG, "Executing stem primary short press action behavior."); } - final boolean keyguardActive = - mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); - if (!keyguardActive) { - Intent intent = new Intent(Intent.ACTION_ALL_APPS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS); + allAppsIntent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + startActivityAsUser(allAppsIntent, UserHandle.CURRENT_OR_SELF); + break; + case SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY: + if (DEBUG_INPUT) { + Slog.d( + TAG, + "Executing stem primary short press action behavior for launching " + + "target activity."); + } + if (mPrimaryShortPressTargetActivity != null) { + Intent targetActivityIntent = new Intent(); + targetActivityIntent.setComponent(mPrimaryShortPressTargetActivity); + ResolveInfo resolveInfo = + mContext.getPackageManager() + .resolveActivity(targetActivityIntent, /* flags= */ 0); + if (resolveInfo != null) { + targetActivityIntent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); + startActivityAsUser(targetActivityIntent, UserHandle.CURRENT_OR_SELF); + } else { + Slog.wtf( + TAG, + "Could not resolve activity with : " + + mPrimaryShortPressTargetActivity.flattenToString() + + " name."); + } } else { - // If keyguarded then notify the keyguard. - mKeyguardDelegate.onSystemKeyPressed(KeyEvent.KEYCODE_STEM_PRIMARY); + Slog.wtf( + TAG, + "mPrimaryShortPressTargetActivity must not be null and correctly" + + " specified"); } break; } @@ -1527,7 +1568,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void stemPrimaryLongPress() { + private void stemPrimaryLongPress(long eventTime) { if (DEBUG_INPUT) { Slog.d(TAG, "Executing stem primary long press action behavior."); } @@ -1536,7 +1577,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { case LONG_PRESS_PRIMARY_NOTHING: break; case LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT: - launchVoiceAssist(/* allowDuringSetup= */false); + final int stemPrimaryKeyDeviceId = INVALID_INPUT_DEVICE_ID; + launchAssistAction( + null, + stemPrimaryKeyDeviceId, + eventTime, + AssistUtils.INVOCATION_TYPE_UNKNOWN); break; } } @@ -2159,6 +2205,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mSensorPrivacyManager = mContext.getSystemService(SensorPrivacyManager.class); + mSearchManager = mContext.getSystemService(SearchManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class); mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); @@ -2295,6 +2342,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerDoublePressTargetActivity = ComponentName.unflattenFromString( mContext.getResources().getString( com.android.internal.R.string.config_doublePressOnPowerTargetActivity)); + mPrimaryShortPressTargetActivity = ComponentName.unflattenFromString( + mContext.getResources().getString( + com.android.internal.R.string.config_primaryShortPressTargetActivity)); mShortPressOnSleepBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnSleepBehavior); mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean( @@ -2658,7 +2708,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override void onLongPress(long eventTime) { - stemPrimaryLongPress(); + stemPrimaryLongPress(eventTime); } @Override @@ -3905,7 +3955,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Add Intent Extra data. Bundle args = null; args = new Bundle(); - if (deviceId > Integer.MIN_VALUE) { + if (deviceId != INVALID_INPUT_DEVICE_ID) { args.putInt(Intent.EXTRA_ASSIST_INPUT_DEVICE_ID, deviceId); } if (hint != null) { @@ -3914,8 +3964,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { args.putLong(Intent.EXTRA_TIME, eventTime); args.putInt(AssistUtils.INVOCATION_TYPE_KEY, invocationType); - ((SearchManager) mContext.createContextAsUser(UserHandle.of(mCurrentUserId), 0) - .getSystemService(Context.SEARCH_SERVICE)).launchAssist(args); + if (mSearchManager != null) { + mSearchManager.launchAssist(args); + } else { + // Fallback to status bar if search manager doesn't exist (e.g. on wear). + StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); + if (statusBar != null) { + statusBar.startAssist(args); + } + } } /** @@ -3926,39 +3983,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean keyguardActive = mKeyguardDelegate != null && mKeyguardDelegate.isShowing(); if (!keyguardActive) { - if (mHasFeatureWatch && isInRetailMode()) { - launchRetailVoiceAssist(allowDuringSetup); - } else { - startVoiceAssistIntent(allowDuringSetup); - } - } else { - mKeyguardDelegate.dismissKeyguardToLaunch(new Intent(Intent.ACTION_VOICE_ASSIST)); - } - } - - private void launchRetailVoiceAssist(boolean allowDuringSetup) { - Intent retailIntent = new Intent(ACTION_VOICE_ASSIST_RETAIL); - ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity( - retailIntent, /* flags= */0); - if (resolveInfo != null) { - retailIntent.setComponent( - new ComponentName(resolveInfo.activityInfo.packageName, - resolveInfo.activityInfo.name)); - startActivityAsUser(retailIntent, null, UserHandle.CURRENT_OR_SELF, + startActivityAsUser( + new Intent(Intent.ACTION_VOICE_ASSIST), + /* bundle= */ null, + UserHandle.CURRENT_OR_SELF, allowDuringSetup); } else { - Slog.w(TAG, "Couldn't find an app to process " + ACTION_VOICE_ASSIST_RETAIL - + ". Fall back to start " + Intent.ACTION_VOICE_ASSIST); - startVoiceAssistIntent(allowDuringSetup); + mKeyguardDelegate.dismissKeyguardToLaunch(new Intent(Intent.ACTION_VOICE_ASSIST)); } } - private void startVoiceAssistIntent(boolean allowDuringSetup) { - Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); - startActivityAsUser(intent, null, UserHandle.CURRENT_OR_SELF, - allowDuringSetup); - } - private boolean isInRetailMode() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0) == 1; @@ -3981,13 +4015,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private SearchManager getSearchManager() { - if (mSearchManager == null) { - mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); - } - return mSearchManager; - } - private void preloadRecentApps() { mPreloadedRecentApps = true; StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); @@ -6471,6 +6498,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { return "SHORT_PRESS_PRIMARY_NOTHING"; case SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS: return "SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS"; + case SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY: + return "SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY"; default: return Integer.toString(behavior); } diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 577468b0c749..33bed3d42e50 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -56,7 +56,6 @@ import java.util.Objects; public final class HintManagerService extends SystemService { private static final String TAG = "HintManagerService"; private static final boolean DEBUG = false; - private static final int MAX_HINT_SESSION_COUNT_PER_UID = 20; @VisibleForTesting final long mHintSessionPreferredRate; // Multi-level map storing all active AppHintSessions. @@ -368,23 +367,6 @@ public final class HintManagerService extends SystemService { + " not be empty."); final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID) { - int sessionCount = 0; - synchronized (mLock) { - ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap = - mActiveSessions.get(callingUid); - if (tokenMap != null) { - for (ArraySet<AppHintSession> arr : tokenMap.values()) { - sessionCount += arr.size(); - } - } - } - if (sessionCount >= MAX_HINT_SESSION_COUNT_PER_UID) { - throw new IllegalStateException( - "Max session count limit reached: " + sessionCount); - } - } - final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java index 2bd7383ddde0..1c5838c165a4 100644 --- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java @@ -105,14 +105,27 @@ public class RemoteProvisioningService extends SystemService { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; - new RemoteProvisioningShellCommand().dump(pw); + final int callerUid = Binder.getCallingUidOrThrow(); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + new RemoteProvisioningShellCommand(getContext(), callerUid).dump(pw); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } } @Override public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, ParcelFileDescriptor err, String[] args) { - return new RemoteProvisioningShellCommand().exec(this, in.getFileDescriptor(), - out.getFileDescriptor(), err.getFileDescriptor(), args); + final int callerUid = Binder.getCallingUidOrThrow(); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + return new RemoteProvisioningShellCommand(getContext(), callerUid).exec(this, + in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), + args); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } } } } diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java index 187b93931f0b..4a6d74658754 100644 --- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java +++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java @@ -16,22 +16,30 @@ package com.android.server.security.rkp; +import android.content.Context; import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.IRemotelyProvisionedComponent; import android.hardware.security.keymint.MacedPublicKey; import android.hardware.security.keymint.ProtectedData; import android.hardware.security.keymint.RpcHardwareInfo; +import android.os.CancellationSignal; +import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ShellCommand; +import android.security.rkp.service.RegistrationProxy; +import android.security.rkp.service.RemotelyProvisionedKey; import android.util.IndentingPrintWriter; -import com.android.internal.annotations.VisibleForTesting; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.time.Duration; import java.util.Base64; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import co.nstant.in.cbor.CborDecoder; import co.nstant.in.cbor.CborEncoder; @@ -54,16 +62,17 @@ class RemoteProvisioningShellCommand extends ShellCommand { + "csr [--challenge CHALLENGE] NAME\n" + " Generate and print a base64-encoded CSR from the named\n" + " IRemotelyProvisionedComponent. A base64-encoded challenge can be provided,\n" - + " or else it defaults to an empty challenge.\n"; + + " or else it defaults to an empty challenge.\n" + + "certify NAME\n" + + " Output the PEM-encoded certificate chain provisioned for the named\n" + + " IRemotelyProvisionedComponent.\n"; - @VisibleForTesting static final String EEK_ED25519_BASE64 = "goRDoQEnoFgqpAEBAycgBiFYIJm57t1e5FL2hcZMYtw+YatXSH11N" + "ymtdoAy0rPLY1jZWEAeIghLpLekyNdOAw7+uK8UTKc7b6XN3Np5xitk/pk5r3bngPpmAIUNB5gqrJFcpyUUS" + "QY0dcqKJ3rZ41pJ6wIDhEOhASegWE6lAQECWCDQrsEVyirPc65rzMvRlh1l6LHd10oaN7lDOpfVmd+YCAM4G" + "CAEIVggvoXnRsSjQlpA2TY6phXQLFh+PdwzAjLS/F4ehyVfcmBYQJvPkOIuS6vRGLEOjl0gJ0uEWP78MpB+c" + "gWDvNeCvvpkeC1UEEvAMb9r6B414vAtzmwvT/L1T6XUg62WovGHWAQ="; - @VisibleForTesting static final String EEK_P256_BASE64 = "goRDoQEmoFhNpQECAyYgASFYIPcUituX9MxT79JkEcTjdR9mH6RxDGzP" + "+glGgHSHVPKtIlggXn9b9uzk9hnM/xM3/Q+hyJPbGAZ2xF3m12p3hsMtr49YQC+XjkL7vgctlUeFR5NAsB/U" + "m0ekxESp8qEHhxDHn8sR9L+f6Dvg5zRMFfx7w34zBfTRNDztAgRgehXgedOK/ySEQ6EBJqBYcaYBAgJYIDVz" @@ -74,14 +83,20 @@ class RemoteProvisioningShellCommand extends ShellCommand { private static final int ERROR = -1; private static final int SUCCESS = 0; + private static final Duration BIND_TIMEOUT = Duration.ofSeconds(10); + private static final int KEY_ID = 452436; + + private final Context mContext; + private final int mCallerUid; private final Injector mInjector; - RemoteProvisioningShellCommand() { - this(new Injector()); + RemoteProvisioningShellCommand(Context context, int callerUid) { + this(context, callerUid, new Injector()); } - @VisibleForTesting - RemoteProvisioningShellCommand(Injector injector) { + RemoteProvisioningShellCommand(Context context, int callerUid, Injector injector) { + mContext = context; + mCallerUid = callerUid; mInjector = injector; } @@ -102,6 +117,8 @@ class RemoteProvisioningShellCommand extends ShellCommand { return list(); case "csr": return csr(); + case "certify": + return certify(); default: return handleDefaultCommands(cmd); } @@ -232,7 +249,45 @@ class RemoteProvisioningShellCommand extends ShellCommand { return new CborDecoder(bais).decodeNext(); } - @VisibleForTesting + private int certify() throws Exception { + String name = getNextArgRequired(); + + Executor executor = mContext.getMainExecutor(); + CancellationSignal cancellationSignal = new CancellationSignal(); + OutcomeFuture<RemotelyProvisionedKey> key = new OutcomeFuture<>(); + mInjector.getRegistrationProxy(mContext, mCallerUid, name, executor) + .getKeyAsync(KEY_ID, cancellationSignal, executor, key); + byte[] encodedCertChain = key.join().getEncodedCertChain(); + ByteArrayInputStream is = new ByteArrayInputStream(encodedCertChain); + PrintWriter pw = getOutPrintWriter(); + for (Certificate cert : CertificateFactory.getInstance("X.509").generateCertificates(is)) { + String encoded = Base64.getEncoder().encodeToString(cert.getEncoded()); + pw.println("-----BEGIN CERTIFICATE-----"); + pw.println(encoded.replaceAll("(.{64})", "$1\n").stripTrailing()); + pw.println("-----END CERTIFICATE-----"); + } + return SUCCESS; + } + + /** Treat an OutcomeReceiver as a future for use in synchronous code. */ + private static class OutcomeFuture<T> implements OutcomeReceiver<T, Exception> { + private CompletableFuture<T> mFuture = new CompletableFuture<>(); + + @Override + public void onResult(T result) { + mFuture.complete(result); + } + + @Override + public void onError(Exception e) { + mFuture.completeExceptionally(e); + } + + public T join() { + return mFuture.join(); + } + } + static class Injector { String[] getIrpcNames() { return ServiceManager.getDeclaredInstances(IRemotelyProvisionedComponent.DESCRIPTOR); @@ -248,5 +303,14 @@ class RemoteProvisioningShellCommand extends ShellCommand { } return binder; } + + RegistrationProxy getRegistrationProxy( + Context context, int callerUid, String name, Executor executor) { + String irpc = IRemotelyProvisionedComponent.DESCRIPTOR + "/" + name; + OutcomeFuture<RegistrationProxy> registration = new OutcomeFuture<>(); + RegistrationProxy.createAsync( + context, callerUid, irpc, BIND_TIMEOUT, executor, registration); + return registration.join(); + } } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 5b36cd6b36c1..ffe001055979 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -155,12 +155,12 @@ public final class TvInputManagerService extends SystemService { // ID of the current user. @GuardedBy("mLock") private int mCurrentUserId = UserHandle.USER_SYSTEM; + // ID of the current on-screen input. @GuardedBy("mLock") - // ID of the current input displayed on the screen. - private String mCurrentInputId = null; + private String mOnScreenInputId = null; + // SessionState of the currently active on-screen TIS session. @GuardedBy("mLock") - // SessionState of the currently active TIS session. - private SessionState mCurrentSessionState = null; + private SessionState mOnScreenSessionState = null; // IDs of the running profiles. Their parent user ID should be mCurrentUserId. @GuardedBy("mLock") private final Set<Integer> mRunningProfiles = new HashSet<>(); @@ -879,12 +879,12 @@ public final class TvInputManagerService extends SystemService { sessionState.session = null; } } - if (mCurrentSessionState == sessionState) { + if (mOnScreenSessionState == sessionState) { // only log when releasing the current on-screen session logExternalInputEvent(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__RELEASED, - mCurrentInputId, sessionState); - mCurrentInputId = null; - mCurrentSessionState = null; + mOnScreenInputId, sessionState); + mOnScreenInputId = null; + mOnScreenSessionState = null; } removeSessionStateLocked(sessionToken, userId); return sessionState; @@ -1079,7 +1079,7 @@ public final class TvInputManagerService extends SystemService { if (currentCecTvInputInfoUpdated) { logExternalInputEvent( FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__DEVICE_INFO_UPDATED, - mCurrentInputId, mCurrentSessionState); + mOnScreenInputId, mOnScreenSessionState); } int n = userState.mCallbacks.beginBroadcast(); @@ -1096,14 +1096,14 @@ public final class TvInputManagerService extends SystemService { @GuardedBy("mLock") private boolean isCurrentCecTvInputInfoUpdate(UserState userState, TvInputInfo newInputInfo) { if (newInputInfo == null || newInputInfo.getId() == null - || !newInputInfo.getId().equals(mCurrentInputId)) { + || !newInputInfo.getId().equals(mOnScreenInputId)) { return false; } if (newInputInfo.getHdmiDeviceInfo() == null || !newInputInfo.getHdmiDeviceInfo().isCecDevice()) { return false; } - TvInputState inputState = userState.inputMap.get(mCurrentInputId); + TvInputState inputState = userState.inputMap.get(mOnScreenInputId); if (inputState == null || inputState.info == null) { return false; } @@ -1133,21 +1133,21 @@ public final class TvInputManagerService extends SystemService { return; } if (oldState != state) { - if (inputId.equals(mCurrentInputId)) { + if (inputId.equals(mOnScreenInputId)) { logExternalInputEvent( FrameworkStatsLog .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__CONNECTION_STATE_CHANGED, - mCurrentInputId, mCurrentSessionState); - } else if (mCurrentInputId != null) { - TvInputInfo currentInputInfo = userState.inputMap.get(mCurrentInputId).info; + mOnScreenInputId, mOnScreenSessionState); + } else if (mOnScreenInputId != null) { + TvInputInfo currentInputInfo = userState.inputMap.get(mOnScreenInputId).info; if (currentInputInfo != null && currentInputInfo.getHdmiDeviceInfo() != null && inputId.equals(currentInputInfo.getParentId())) { logExternalInputEvent( FrameworkStatsLog .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__CONNECTION_STATE_CHANGED, - inputId, mCurrentSessionState); + inputId, mOnScreenSessionState); if (state == INPUT_STATE_CONNECTED_STANDBY) { - mCurrentInputId = currentInputInfo.getParentId(); + mOnScreenInputId = currentInputInfo.getParentId(); } } } @@ -1814,19 +1814,22 @@ public final class TvInputManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(resolvedUserId); SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userState); - if (mCurrentInputId == null - || !mCurrentInputId.equals(sessionState.inputId)) { - mCurrentInputId = sessionState.inputId; - logExternalInputEvent( - FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED, - sessionState.inputId, sessionState); - } if (!sessionState.isCurrent || !Objects.equals(sessionState.currentChannel, channelUri)) { sessionState.isCurrent = true; sessionState.currentChannel = channelUri; - mCurrentSessionState = sessionState; notifyCurrentChannelInfosUpdatedLocked(userState); + if (!sessionState.isRecordingSession) { + if (mOnScreenInputId == null + || !TextUtils.equals(mOnScreenInputId, sessionState.inputId)) { + logExternalInputEvent( + FrameworkStatsLog + .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED, + sessionState.inputId, sessionState); + } + mOnScreenInputId = sessionState.inputId; + mOnScreenSessionState = sessionState; + } } if (TvContract.isChannelUriForPassthroughInput(channelUri)) { // Do not log the watch history for passthrough inputs. @@ -3436,21 +3439,21 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { mTvInputHardwareManager.addHdmiInput(id, inputInfo); addHardwareInputLocked(inputInfo); - if (mCurrentInputId != null && mCurrentSessionState != null) { - if (TextUtils.equals(mCurrentInputId, inputInfo.getParentId())) { + 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, // and TV app does not explicitly call tune() to the added CEC input. logExternalInputEvent( FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED, - inputInfo.getId(), mCurrentSessionState); - mCurrentInputId = inputInfo.getId(); - } else if (TextUtils.equals(mCurrentInputId, inputInfo.getId())) { + inputInfo.getId(), mOnScreenSessionState); + mOnScreenInputId = inputInfo.getId(); + } else if (TextUtils.equals(mOnScreenInputId, inputInfo.getId())) { // catch the use case when a CEC device disconnects itself // and reconnects to update info. logExternalInputEvent( FrameworkStatsLog .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__DEVICE_INFO_UPDATED, - mCurrentInputId, mCurrentSessionState); + mOnScreenInputId, mOnScreenSessionState); } } } @@ -3555,9 +3558,17 @@ public final class TvInputManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(mSessionState.userId); mSessionState.isCurrent = true; mSessionState.currentChannel = channelUri; - mCurrentSessionState = mSessionState; - mCurrentInputId = mSessionState.inputId; notifyCurrentChannelInfosUpdatedLocked(userState); + if (!mSessionState.isRecordingSession) { + if (mOnScreenInputId == null + || !TextUtils.equals(mOnScreenInputId, mSessionState.inputId)) { + logExternalInputEvent( + FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED, + mSessionState.inputId, mSessionState); + } + mOnScreenInputId = mSessionState.inputId; + mOnScreenSessionState = mSessionState; + } } } catch (RemoteException e) { Slog.e(TAG, "error in onChannelRetuned", e); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 64c7c6f9875b..649ab8fe1d4d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7258,8 +7258,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void showStartingWindow(boolean taskSwitch) { - showStartingWindow(null /* prev */, false /* newTask */, taskSwitch, - false /* startActivity */, null); + // Pass the activity which contains starting window already. + final ActivityRecord prev = task.getActivity( + a -> a != this && a.mStartingData != null && a.showToCurrentUser()); + showStartingWindow(prev, false /* newTask */, taskSwitch, false /* startActivity */, null); } /** diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5c82dba82031..59677f41123a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -327,6 +327,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private SurfaceControl mOverlayLayer; + /** + * A SurfaceControl that contains input overlays used for cases where we need to receive input + * over the entire display. + */ + private SurfaceControl mInputOverlayLayer; + /** A surfaceControl specifically for accessibility overlays. */ private SurfaceControl mA11yOverlayLayer; @@ -1327,6 +1333,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp transaction.reparent(mOverlayLayer, mSurfaceControl); } + if (mInputOverlayLayer == null) { + mInputOverlayLayer = b.setName("Input Overlays").setParent(mSurfaceControl).build(); + } else { + transaction.reparent(mInputOverlayLayer, mSurfaceControl); + } + if (mA11yOverlayLayer == null) { mA11yOverlayLayer = b.setName("Accessibility Overlays").setParent(mSurfaceControl).build(); @@ -1340,7 +1352,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .show(mSurfaceControl) .setLayer(mOverlayLayer, Integer.MAX_VALUE) .show(mOverlayLayer) - .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 1) + .setLayer(mInputOverlayLayer, Integer.MAX_VALUE - 1) + .show(mInputOverlayLayer) + .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 2) .show(mA11yOverlayLayer); } @@ -3351,6 +3365,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // -> this DisplayContent. setRemoteInsetsController(null); mOverlayLayer.release(); + mInputOverlayLayer.release(); mA11yOverlayLayer.release(); mWindowingLayer.release(); mInputMonitor.onDisplayRemoved(); @@ -5704,6 +5719,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mOverlayLayer; } + SurfaceControl getInputOverlayLayer() { + return mInputOverlayLayer; + } + SurfaceControl getA11yOverlayLayer() { return mA11yOverlayLayer; } @@ -7060,6 +7079,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp new Transaction().reparent(sc, getSurfaceControl()) .reparent(mWindowingLayer, null) .reparent(mOverlayLayer, null) + .reparent(mInputOverlayLayer, null) .reparent(mA11yOverlayLayer, null) .apply(); } diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 73fdfe0d1181..8cf471394c63 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -275,11 +275,17 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal + " - DisplayContent not found."); return null; } + final SurfaceControl inputOverlay = dc.getInputOverlayLayer(); + if (inputOverlay == null) { + Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId + + " - Input overlay layer is not initialized."); + return null; + } return mService.makeSurfaceBuilder(dc.getSession()) .setContainerLayer() .setName(name) .setCallsite("createSurfaceForGestureMonitor") - .setParent(dc.getSurfaceControl()) + .setParent(inputOverlay) .build(); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2f0c303ec839..4089a0d7622f 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2277,7 +2277,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> int finishTopCrashedActivities(WindowProcessController app, String reason) { Task focusedRootTask = getTopDisplayFocusedRootTask(); final Task[] finishedTask = new Task[1]; - forAllTasks(rootTask -> { + forAllRootTasks(rootTask -> { final Task t = rootTask.finishTopCrashedActivityLocked(app, reason); if (rootTask == focusedRootTask || finishedTask[0] == null) { finishedTask[0] = t; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 43430dd1eed0..f9bbc6810835 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -725,7 +725,7 @@ class Task extends TaskFragment { } catch (RemoteException e) { } } - if (autoRemoveFromRecents(oldParent.asTaskFragment()) || isVoiceSession) { + if (shouldAutoRemoveFromRecents(oldParent.asTaskFragment()) || isVoiceSession) { // Task creator asked to remove this when done, or this task was a voice // interaction, so it should not remain on the recent tasks list. mTaskSupervisor.mRecentTasks.remove(this); @@ -1558,12 +1558,14 @@ class Task extends TaskFragment { return count > 0; } - private boolean autoRemoveFromRecents(TaskFragment oldParentFragment) { + private boolean shouldAutoRemoveFromRecents(TaskFragment oldParentFragment) { // We will automatically remove the task either if it has explicitly asked for // this, or it is empty and has never contained an activity that got shown to - // the user, or it was being embedded in another Task. - return autoRemoveRecents || (!hasChild() && !getHasBeenVisible() - || (oldParentFragment != null && oldParentFragment.isEmbedded())); + // the user, or it was being embedded in another Task, or the display policy + // doesn't allow recents, + return autoRemoveRecents || (!hasChild() && !getHasBeenVisible()) + || (oldParentFragment != null && oldParentFragment.isEmbedded()) + || (mDisplayContent != null && !mDisplayContent.canShowTasksInHostDeviceRecents()); } private void clearPinnedTaskIfNeed() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 03efb1bf705e..439b7193dd4b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8338,12 +8338,18 @@ public class WindowManagerService extends IWindowManager.Stub + displayId + " - DisplayContent not found."); return null; } - //TODO (b/210039666): Use a method like add/removeDisplayOverlay if available. + final SurfaceControl inputOverlay = dc.getInputOverlayLayer(); + if (inputOverlay == null) { + Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId + + " - Input overlay layer is not initialized."); + return null; + } + // TODO(b/210039666): Use a method like add/removeDisplayOverlay if available. return makeSurfaceBuilder(dc.getSession()) .setContainerLayer() .setName("IME Handwriting Surface") .setCallsite("getHandwritingSurfaceForDisplay") - .setParent(dc.getSurfaceControl()) + .setParent(inputOverlay) .build(); } } diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 7104a80c668d..d833fbd6ffd2 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -459,16 +459,26 @@ <xs:complexType name="autoBrightness"> <xs:attribute name="enabled" type="xs:boolean" use="optional" default="true"/> <xs:sequence> - <!-- Sets the debounce for autoBrightness brightening in millis--> + <!-- Sets the debounce for autoBrightness brightening in millis --> <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> - <!-- Sets the debounce for autoBrightness darkening in millis--> + <!-- Sets the debounce for autoBrightness darkening in millis --> <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> + <!-- Sets the debounce for autoBrightness brightening in millis while in idle mode --> + <xs:element name="brighteningLightDebounceIdleMillis" type="xs:nonNegativeInteger" + minOccurs="0" maxOccurs="1"> + <xs:annotation name="final"/> + </xs:element> + <!-- Sets the debounce for autoBrightness darkening in millis while in idle mode --> + <xs:element name="darkeningLightDebounceIdleMillis" type="xs:nonNegativeInteger" + minOccurs="0" maxOccurs="1"> + <xs:annotation name="final"/> + </xs:element> <!-- Sets the brightness mapping of the desired screen brightness in nits to the corresponding lux for the current display --> <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping" @@ -579,6 +589,10 @@ minOccurs="1" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> + <xs:element type ="xs:string" name="refreshRateThermalThrottlingId"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> <xs:element name="blockingZoneThreshold" type="blockingZoneThreshold" minOccurs="1" maxOccurs="1"> <xs:annotation name="final"/> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 507c9dccda59..d2ac1aae1500 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -3,11 +3,15 @@ package com.android.server.display.config { public class AutoBrightness { ctor public AutoBrightness(); + method public final java.math.BigInteger getBrighteningLightDebounceIdleMillis(); method public final java.math.BigInteger getBrighteningLightDebounceMillis(); + method public final java.math.BigInteger getDarkeningLightDebounceIdleMillis(); method public final java.math.BigInteger getDarkeningLightDebounceMillis(); method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping(); method public boolean getEnabled(); + method public final void setBrighteningLightDebounceIdleMillis(java.math.BigInteger); method public final void setBrighteningLightDebounceMillis(java.math.BigInteger); + method public final void setDarkeningLightDebounceIdleMillis(java.math.BigInteger); method public final void setDarkeningLightDebounceMillis(java.math.BigInteger); method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping); method public void setEnabled(boolean); @@ -17,8 +21,10 @@ package com.android.server.display.config { ctor public BlockingZoneConfig(); method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold(); method public final java.math.BigInteger getDefaultRefreshRate(); + method @Nullable public final String getRefreshRateThermalThrottlingId(); method public final void setBlockingZoneThreshold(com.android.server.display.config.BlockingZoneThreshold); method public final void setDefaultRefreshRate(java.math.BigInteger); + method public final void setRefreshRateThermalThrottlingId(@Nullable String); } public class BlockingZoneThreshold { diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt index 17474fbe1de4..6a349e237ffe 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt @@ -26,6 +26,7 @@ import com.android.server.permission.access.collection.* // ktlint-disable no-wi import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports import com.android.server.permission.access.immutable.IndexedMap import com.android.server.permission.access.permission.AppIdPermissionPolicy +import com.android.server.permission.access.permission.DevicePermissionPolicy import com.android.server.permission.access.util.attributeInt import com.android.server.permission.access.util.attributeInterned import com.android.server.permission.access.util.forEachTag @@ -46,6 +47,7 @@ class AccessPolicy private constructor( getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy } addPolicy(AppIdPermissionPolicy()) + addPolicy(DevicePermissionPolicy()) addPolicy(AppIdAppOpPolicy()) addPolicy(PackageAppOpPolicy()) } as IndexedMap<String, IndexedMap<String, SchemePolicy>> diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt index 4ec32ea53f28..94c878a453c9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessState.kt +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -329,6 +329,18 @@ typealias MutableAppIdPermissionFlags = private typealias AppIdPermissionFlagsReference = MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags> + +typealias DevicePermissionFlags = + IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> +typealias MutableDevicePermissionFlags = + MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> +typealias AppIdDevicePermissionFlags = + IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> +typealias MutableAppIdDevicePermissionFlags = + MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> +private typealias AppIdDevicePermissionFlagsReference = + MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags> + typealias AppIdAppOpModes = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> typealias MutableAppIdAppOpModes = @@ -346,6 +358,7 @@ private typealias PackageAppOpModesReference = sealed class UserState( internal val packageVersionsReference: PackageVersionsReference, internal val appIdPermissionFlagsReference: AppIdPermissionFlagsReference, + internal val appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference, internal val appIdAppOpModesReference: AppIdAppOpModesReference, internal val packageAppOpModesReference: PackageAppOpModesReference, defaultPermissionGrantFingerprint: String?, @@ -357,6 +370,9 @@ sealed class UserState( val appIdPermissionFlags: AppIdPermissionFlags get() = appIdPermissionFlagsReference.get() + val appIdDevicePermissionFlags: AppIdDevicePermissionFlags + get() = appIdDevicePermissionFlagsReference.get() + val appIdAppOpModes: AppIdAppOpModes get() = appIdAppOpModesReference.get() @@ -375,6 +391,7 @@ sealed class UserState( class MutableUserState private constructor( packageVersionsReference: PackageVersionsReference, appIdPermissionFlagsReference: AppIdPermissionFlagsReference, + appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference, appIdAppOpModesReference: AppIdAppOpModesReference, packageAppOpModesReference: PackageAppOpModesReference, defaultPermissionGrantFingerprint: String?, @@ -382,6 +399,7 @@ class MutableUserState private constructor( ) : UserState( packageVersionsReference, appIdPermissionFlagsReference, + appIdDevicePermissionFlagsReference, appIdAppOpModesReference, packageAppOpModesReference, defaultPermissionGrantFingerprint, @@ -390,6 +408,7 @@ class MutableUserState private constructor( constructor() : this( PackageVersionsReference(MutableIndexedMap<String, Int>()), AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), + AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), AppIdAppOpModesReference(MutableAppIdAppOpModes()), PackageAppOpModesReference(MutablePackageAppOpModes()), null, @@ -399,6 +418,7 @@ class MutableUserState private constructor( internal constructor(userState: UserState) : this( userState.packageVersionsReference.toImmutable(), userState.appIdPermissionFlagsReference.toImmutable(), + userState.appIdDevicePermissionFlagsReference.toImmutable(), userState.appIdAppOpModesReference.toImmutable(), userState.packageAppOpModesReference.toImmutable(), userState.defaultPermissionGrantFingerprint, @@ -410,6 +430,9 @@ class MutableUserState private constructor( fun mutateAppIdPermissionFlags(): MutableAppIdPermissionFlags = appIdPermissionFlagsReference.mutate() + fun mutateAppIdDevicePermissionFlags(): MutableAppIdDevicePermissionFlags = + appIdDevicePermissionFlagsReference.mutate() + fun mutateAppIdAppOpModes(): MutableAppIdAppOpModes = appIdAppOpModesReference.mutate() fun mutatePackageAppOpModes(): MutablePackageAppOpModes = packageAppOpModesReference.mutate() diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt index d1abc0455245..1d46ca71fe0f 100644 --- a/services/permission/java/com/android/server/permission/access/AccessUri.kt +++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt @@ -65,6 +65,17 @@ data class PermissionUri( } } +data class DevicePermissionUri( + val permissionName: String, + val deviceId: Int +) : AccessUri(SCHEME) { + override fun toString(): String = "$scheme:///$permissionName/$deviceId" + + companion object { + const val SCHEME = "device-permission" + } +} + data class UidUri( val uid: Int ) : AccessUri(SCHEME) { diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt new file mode 100644 index 000000000000..37a4a90f8f80 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt @@ -0,0 +1,169 @@ +/* + * 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.server.permission.access.permission + +import android.util.Slog +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.AccessState +import com.android.server.permission.access.DevicePermissionFlags +import com.android.server.permission.access.MutableAccessState +import com.android.server.permission.access.MutableAppIdDevicePermissionFlags +import com.android.server.permission.access.MutableDevicePermissionFlags +import com.android.server.permission.access.WriteMode +import com.android.server.permission.access.immutable.IndexedMap +import com.android.server.permission.access.immutable.MutableIndexedMap +import com.android.server.permission.access.immutable.forEachIndexed +import com.android.server.permission.access.immutable.forEachReversedIndexed +import com.android.server.permission.access.immutable.set +import com.android.server.permission.access.util.andInv +import com.android.server.permission.access.util.attributeInt +import com.android.server.permission.access.util.attributeInterned +import com.android.server.permission.access.util.forEachTag +import com.android.server.permission.access.util.getAttributeIntOrThrow +import com.android.server.permission.access.util.getAttributeValueOrThrow +import com.android.server.permission.access.util.hasBits +import com.android.server.permission.access.util.tag +import com.android.server.permission.access.util.tagName + +class DevicePermissionPersistence { + fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) { + when (tagName) { + TAG_APP_ID_DEVICE_PERMISSIONS -> parseAppIdDevicePermissions(state, userId) + else -> {} + } + } + + private fun BinaryXmlPullParser.parseAppIdDevicePermissions( + state: MutableAccessState, + userId: Int + ) { + val userState = state.mutateUserState(userId, WriteMode.NONE)!! + val appIdDevicePermissionFlags = userState.mutateAppIdDevicePermissionFlags() + forEachTag { + when (tagName) { + TAG_APP_ID -> parseAppId(appIdDevicePermissionFlags) + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") + } + } + + appIdDevicePermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ -> + if (appId !in state.externalState.appIdPackageNames) { + Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state") + appIdDevicePermissionFlags.removeAt(appIdIndex) + userState.requestWriteMode(WriteMode.ASYNCHRONOUS) + } + } + } + + private fun BinaryXmlPullParser.parseAppId( + appIdPermissionFlags: MutableAppIdDevicePermissionFlags + ) { + val appId = getAttributeIntOrThrow(ATTR_ID) + val devicePermissionFlags = MutableDevicePermissionFlags() + appIdPermissionFlags[appId] = devicePermissionFlags + forEachTag { + when (tagName) { + TAG_DEVICE -> parseDevice(devicePermissionFlags) + else -> { + Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") + } + } + } + } + + private fun BinaryXmlPullParser.parseDevice( + deviceIdPermissionFlags: MutableDevicePermissionFlags + ) { + val deviceId = getAttributeValueOrThrow(ATTR_ID) + val permissionFlags = MutableIndexedMap<String, Int>() + deviceIdPermissionFlags.put(deviceId, permissionFlags) + forEachTag { + when (tagName) { + TAG_PERMISSION -> parsePermission(permissionFlags) + else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state") + } + } + } + + private fun BinaryXmlPullParser.parsePermission( + permissionFlags: MutableIndexedMap<String, Int> + ) { + val name = getAttributeValueOrThrow(ATTR_NAME).intern() + val flags = getAttributeIntOrThrow(ATTR_FLAGS) + permissionFlags[name] = flags + } + + fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) { + val appIdDevicePermissionFlags = state.userStates[userId]!!.appIdDevicePermissionFlags + tag(TAG_APP_ID_DEVICE_PERMISSIONS) { + appIdDevicePermissionFlags.forEachIndexed { _, appId, devicePermissionFlags -> + serializeAppId(appId, devicePermissionFlags) + } + } + } + + private fun BinaryXmlSerializer.serializeAppId( + appId: Int, + devicePermissionFlags: DevicePermissionFlags + ) { + tag(TAG_APP_ID) { + attributeInt(ATTR_ID, appId) + devicePermissionFlags.forEachIndexed { _, deviceId, permissionFlags -> + serializeDevice(deviceId, permissionFlags) + } + } + } + + private fun BinaryXmlSerializer.serializeDevice( + deviceId: String, + permissionFlags: IndexedMap<String, Int> + ) { + tag(TAG_DEVICE) { + attributeInterned(ATTR_ID, deviceId) + permissionFlags.forEachIndexed { _, name, flags -> + serializePermission(name, flags) + } + } + } + + private fun BinaryXmlSerializer.serializePermission(name: String, flags: Int) { + tag(TAG_PERMISSION) { + attributeInterned(ATTR_NAME, name) + // Never serialize one-time permissions as granted. + val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } + attributeInt(ATTR_FLAGS, serializedFlags) + } + } + + companion object { + private val LOG_TAG = DevicePermissionPersistence::class.java.simpleName + + private const val TAG_APP_ID_DEVICE_PERMISSIONS = "app-id-device-permissions" + private const val TAG_APP_ID = "app-id" + private const val TAG_DEVICE = "device" + private const val TAG_PERMISSION = "permission" + + private const val ATTR_ID = "id" + private const val ATTR_NAME = "name" + private const val ATTR_FLAGS = "flags" + } +}
\ No newline at end of file diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt new file mode 100644 index 000000000000..c0d7546180bf --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -0,0 +1,276 @@ +/* + * 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.server.permission.access.permission + +import android.util.Slog +import com.android.modules.utils.BinaryXmlPullParser +import com.android.modules.utils.BinaryXmlSerializer +import com.android.server.permission.access.AccessState +import com.android.server.permission.access.DevicePermissionUri +import com.android.server.permission.access.GetStateScope +import com.android.server.permission.access.MutableAccessState +import com.android.server.permission.access.MutateStateScope +import com.android.server.permission.access.SchemePolicy +import com.android.server.permission.access.UidUri +import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.util.andInv +import com.android.server.pm.pkg.PackageState + +class DevicePermissionPolicy : SchemePolicy() { + private val persistence = DevicePermissionPersistence() + + @Volatile + private var listeners: IndexedListSet<OnDevicePermissionFlagsChangedListener> = + MutableIndexedListSet() + private val listenersLock = Any() + + override val subjectScheme: String + get() = UidUri.SCHEME + + override val objectScheme: String + get() = DevicePermissionUri.SCHEME + + override fun GetStateScope.onStateMutated() { + listeners.forEachIndexed { _, it -> it.onStateMutated() } + } + + override fun MutateStateScope.onAppIdRemoved(appId: Int) { + newState.userStates.forEachIndexed { userStateIndex, _, userState -> + if (appId in userState.appIdDevicePermissionFlags) { + newState.mutateUserStateAt(userStateIndex) + .mutateAppIdDevicePermissionFlags() -= appId + } + } + } + + override fun MutateStateScope.onStorageVolumeMounted( + volumeUuid: String?, + packageNames: List<String>, + isSystemUpdated: Boolean + ) { + packageNames.forEachIndexed { _, packageName -> + val packageState = newState.externalState.packageStates[packageName]!! + trimPermissionStates(packageState.appId) + } + } + + override fun MutateStateScope.onPackageAdded(packageState: PackageState) { + trimPermissionStates(packageState.appId) + } + + override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) { + if (appId in newState.externalState.appIdPackageNames) { + trimPermissionStates(appId) + } + } + + override fun MutateStateScope.onPackageUninstalled( + packageName: String, + appId: Int, + userId: Int + ) { + resetPermissionStates(packageName, userId) + } + + private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) { + // It's okay to skip resetting permissions for packages that are removed, + // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved() + val packageState = newState.externalState.packageStates[packageName] ?: return + val androidPackage = packageState.androidPackage ?: return + val appId = packageState.appId + val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags + androidPackage.requestedPermissions.forEach { permissionName -> + val isRequestedByOtherPackages = anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } + if (isRequestedByOtherPackages) { + return@forEach + } + appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ -> + setPermissionFlags(appId, deviceId, userId, permissionName, 0) + } + } + } + + private fun MutateStateScope.trimPermissionStates(appId: Int) { + val requestedPermissions = MutableIndexedSet<String>() + forEachPackageInAppId(appId) { + requestedPermissions += it.androidPackage!!.requestedPermissions + } + newState.userStates.forEachIndexed { _, userId, userState -> + userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed { + _, deviceId, permissionFlags -> + permissionFlags.forEachReversedIndexed { _, permissionName, _ -> + if (permissionName !in requestedPermissions) { + setPermissionFlags(appId, deviceId, userId, permissionName, 0) + } + } + } + } + } + + private inline fun MutateStateScope.anyPackageInAppId( + appId: Int, + state: AccessState = newState, + predicate: (PackageState) -> Boolean + ): Boolean { + val packageNames = state.externalState.appIdPackageNames[appId]!! + return packageNames.anyIndexed { _, packageName -> + val packageState = state.externalState.packageStates[packageName]!! + packageState.androidPackage != null && predicate(packageState) + } + } + + private inline fun MutateStateScope.forEachPackageInAppId( + appId: Int, + state: AccessState = newState, + action: (PackageState) -> Unit + ) { + val packageNames = state.externalState.appIdPackageNames[appId]!! + packageNames.forEachIndexed { _, packageName -> + val packageState = state.externalState.packageStates[packageName]!! + if (packageState.androidPackage != null) { + action(packageState) + } + } + } + + override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) { + with(persistence) { this@parseUserState.parseUserState(state, userId) } + } + + override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) { + with(persistence) { this@serializeUserState.serializeUserState(state, userId) } + } + + fun GetStateScope.getPermissionFlags( + appId: Int, + deviceId: String, + userId: Int, + permissionName: String + ): Int = + state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId) + ?.getWithDefault(permissionName, 0) ?: 0 + + fun MutateStateScope.setPermissionFlags( + appId: Int, + deviceId: String, + userId: Int, + permissionName: String, + flags: Int + ): Boolean = + updatePermissionFlags( + appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags + ) + + private fun MutateStateScope.updatePermissionFlags( + appId: Int, + deviceId: String, + userId: Int, + permissionName: String, + flagMask: Int, + flagValues: Int + ): Boolean { + if (!isDeviceAwarePermission(permissionName)) { + Slog.w(LOG_TAG, "$permissionName is not a device aware permission.") + return false + } + val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId] + ?.get(deviceId).getWithDefault(permissionName, 0) + val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) + if (oldFlags == newFlags) { + return false + } + val appIdDevicePermissionFlags = + newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags() + val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) { + MutableIndexedReferenceMap() + } + val permissionFlags = devicePermissionFlags.mutateOrPut(deviceId) { MutableIndexedMap() } + permissionFlags.putWithDefault(permissionName, newFlags, 0) + if (permissionFlags.isEmpty()) { + devicePermissionFlags -= deviceId + if (devicePermissionFlags.isEmpty()) { + appIdDevicePermissionFlags -= appId + } + } + listeners.forEachIndexed { _, it -> + it.onDevicePermissionFlagsChanged( + appId, userId, deviceId, permissionName, oldFlags, newFlags + ) + } + return true + } + + fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { + synchronized(listenersLock) { + listeners = listeners + listener + } + } + + fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { + synchronized(listenersLock) { + listeners = listeners - listener + } + } + + private fun isDeviceAwarePermission(permissionName: String): Boolean = + DEVICE_SUPPORTED_PERMISSIONS.contains(permissionName) + + companion object { + private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName + + /** + * These permissions are supported for virtual devices. + */ + private val DEVICE_SUPPORTED_PERMISSIONS = indexedSetOf( + android.Manifest.permission.CAMERA, + android.Manifest.permission.RECORD_AUDIO + ) + } + + /** + * TODO: b/289355341 - implement listener for permission changes + * Listener for permission flags changes. + */ + abstract class OnDevicePermissionFlagsChangedListener { + /** + * Called when a permission flags change has been made to the upcoming new state. + * + * Implementations should keep this method fast to avoid stalling the locked state mutation, + * and only call external code after [onStateMutated] when the new state has actually become + * the current state visible to external code. + */ + abstract fun onDevicePermissionFlagsChanged( + appId: Int, + userId: Int, + deviceId: String, + permissionName: String, + oldFlags: Int, + newFlags: Int + ) + + /** + * Called when the upcoming new state has become the current state. + * + * Implementations should keep this method fast to avoid stalling the locked state mutation. + */ + abstract fun onStateMutated() + } +}
\ No newline at end of file diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index edacf188b333..f0705eda5c43 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -64,9 +64,11 @@ import com.android.server.LocalServices import com.android.server.PermissionThread import com.android.server.ServiceThread import com.android.server.SystemConfig +import com.android.server.companion.virtual.VirtualDeviceManagerInternal import com.android.server.permission.access.AccessCheckingService import com.android.server.permission.access.AccessState import com.android.server.permission.access.AppOpUri +import com.android.server.permission.access.DevicePermissionUri import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.MutateStateScope import com.android.server.permission.access.PermissionUri @@ -110,6 +112,9 @@ class PermissionService( private val policy = service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy + private val devicePolicy = + service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy + private val context = service.context private lateinit var metricsLogger: MetricsLogger private lateinit var packageManagerInternal: PackageManagerInternal @@ -130,6 +135,8 @@ class PermissionService( @GuardedBy("storageVolumeLock") private val storageVolumePackageNames = ArrayMap<String?, MutableList<String>>() + private var virtualDeviceManagerInternal: VirtualDeviceManagerInternal? = null + private lateinit var permissionControllerManager: PermissionControllerManager /** @@ -152,7 +159,6 @@ class PermissionService( systemConfig = SystemConfig.getInstance() userManagerInternal = LocalServices.getService(UserManagerInternal::class.java) userManagerService = UserManagerService.getInstance() - // The package info cache is the cache for package and permission information. // Disable the package info and package permission caches locally but leave the // checkPermission cache active. @@ -460,7 +466,7 @@ class PermissionService( return size } - override fun checkUidPermission(uid: Int, permissionName: String): Int { + override fun checkUidPermission(uid: Int, permissionName: String, deviceId: Int): Int { val userId = UserHandle.getUserId(uid) if (!userManagerInternal.exists(userId)) { return PackageManager.PERMISSION_DENIED @@ -482,7 +488,7 @@ class PermissionService( return PackageManager.PERMISSION_DENIED } val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName) + isPermissionGranted(packageState, userId, permissionName, deviceId) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED @@ -515,7 +521,12 @@ class PermissionService( return false } - override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int { + override fun checkPermission( + packageName: String, + permissionName: String, + deviceId: Int, + userId: Int + ): Int { if (!userManagerInternal.exists(userId)) { return PackageManager.PERMISSION_DENIED } @@ -524,7 +535,7 @@ class PermissionService( .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName) + isPermissionGranted(packageState, userId, permissionName, deviceId) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED @@ -542,19 +553,21 @@ class PermissionService( private fun GetStateScope.isPermissionGranted( packageState: PackageState, userId: Int, - permissionName: String + permissionName: String, + deviceId: Int ): Boolean { val appId = packageState.appId // Note that instant apps can't have shared UIDs, so we only need to check the current // package state. val isInstantApp = packageState.getUserStateOrDefault(userId).isInstantApp - if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName)) { + if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName, deviceId)) { return true } val fullerPermissionName = FULLER_PERMISSIONS[permissionName] if (fullerPermissionName != null && - isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName)) { + isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId) + ) { return true } @@ -568,9 +581,10 @@ class PermissionService( appId: Int, userId: Int, isInstantApp: Boolean, - permissionName: String + permissionName: String, + deviceId: Int, ): Boolean { - val flags = with(policy) { getPermissionFlags(appId, userId, permissionName) } + val flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (!PermissionFlags.isPermissionGranted(flags)) { return false } @@ -601,7 +615,8 @@ class PermissionService( ?: return emptySet() return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ -> - if (isPermissionGranted(packageState, userId, permissionName)) { + if (isPermissionGranted( + packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) { permissionName } else { null @@ -640,18 +655,26 @@ class PermissionService( } } - override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) { - setRuntimePermissionGranted(packageName, userId, permissionName, isGranted = true) + override fun grantRuntimePermission( + packageName: String, + permissionName: String, + deviceId: Int, + userId: Int + ) { + setRuntimePermissionGranted( + packageName, userId, permissionName, deviceId, isGranted = true + ) } override fun revokeRuntimePermission( packageName: String, permissionName: String, + deviceId: Int, userId: Int, reason: String? ) { setRuntimePermissionGranted( - packageName, userId, permissionName, isGranted = false, revokeReason = reason + packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason ) } @@ -660,8 +683,8 @@ class PermissionService( userId: Int ) { setRuntimePermissionGranted( - packageName, userId, Manifest.permission.POST_NOTIFICATIONS, isGranted = false, - skipKillUid = true + packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, + isGranted = false, skipKillUid = true ) } @@ -673,6 +696,7 @@ class PermissionService( packageName: String, userId: Int, permissionName: String, + deviceId: Int, isGranted: Boolean, skipKillUid: Boolean = false, revokeReason: String? = null @@ -748,7 +772,7 @@ class PermissionService( } setRuntimePermissionGranted( - packageState, userId, permissionName, isGranted, canManageRolePermission, + packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission, overridePolicyFixed, reportError = true, methodName ) } @@ -782,14 +806,16 @@ class PermissionService( if (permissionState == PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) { setRuntimePermissionGranted( - packageState, userId, permissionName, isGranted = true, - canManageRolePermission = false, overridePolicyFixed = false, - reportError = false, "setRequestedPermissionStates" + packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT, + isGranted = true, canManageRolePermission = false, + overridePolicyFixed = false, reportError = false, + "setRequestedPermissionStates" ) updatePermissionFlags( packageState.appId, userId, permissionName, + Context.DEVICE_ID_DEFAULT, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or - PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, + PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, canUpdateSystemFlags = false, reportErrorForUnknownPermission = false, isPermissionRequested = true, "setRequestedPermissionStates", @@ -816,6 +842,7 @@ class PermissionService( packageState: PackageState, userId: Int, permissionName: String, + deviceId: Int, isGranted: Boolean, canManageRolePermission: Boolean, overridePolicyFixed: Boolean, @@ -871,7 +898,7 @@ class PermissionService( } val appId = packageState.appId - val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) } + val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) { if (reportError) { @@ -934,7 +961,7 @@ class PermissionService( return } - with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) } + setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) if (permission.isRuntime) { val action = if (isGranted) { @@ -958,12 +985,17 @@ class PermissionService( ) { val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy - val appOpName = AppOpsManager.permissionToOp(permissionName) + val appOpName = checkNotNull(AppOpsManager.permissionToOp(permissionName)) val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) } } - override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int { + override fun getPermissionFlags( + packageName: String, + permissionName: String, + deviceId: Int, + userId: Int, + ): Int { if (!userManagerInternal.exists(userId)) { Slog.w(LOG_TAG, "getPermissionFlags: Unknown user $userId") return 0 @@ -994,7 +1026,8 @@ class PermissionService( } val flags = - with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId) + return PermissionFlags.toApiFlags(flags) } } @@ -1002,6 +1035,7 @@ class PermissionService( override fun isPermissionRevokedByPolicy( packageName: String, permissionName: String, + deviceId: Int, userId: Int ): Boolean { if (!userManagerInternal.exists(userId)) { @@ -1018,13 +1052,13 @@ class PermissionService( .use { it.getPackageState(packageName) } ?: return false service.getState { - if (isPermissionGranted(packageState, userId, permissionName)) { + if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { return false } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) - } + val flags = + getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId) + return flags.hasBits(PermissionFlags.POLICY_FIXED) } } @@ -1046,7 +1080,8 @@ class PermissionService( override fun shouldShowRequestPermissionRationale( packageName: String, permissionName: String, - userId: Int + deviceId: Int, + userId: Int, ): Boolean { if (!userManagerInternal.exists(userId)) { Slog.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId") @@ -1068,11 +1103,11 @@ class PermissionService( val flags: Int service.getState { - if (isPermissionGranted(packageState, userId, permissionName)) { + if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { return false } - flags = with(policy) { getPermissionFlags(appId, userId, permissionName) } + flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) } if (flags.hasAnyBit(UNREQUESTABLE_MASK)) { return false @@ -1104,6 +1139,7 @@ class PermissionService( flagMask: Int, flagValues: Int, enforceAdjustPolicyPermission: Boolean, + deviceId: Int, userId: Int ) { val callingUid = Binder.getCallingUid() @@ -1199,7 +1235,7 @@ class PermissionService( val appId = packageState.appId service.mutateState { updatePermissionFlags( - appId, userId, permissionName, flagMask, flagValues, canUpdateSystemFlags, + appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags, reportErrorForUnknownPermission = true, isPermissionRequested, "updatePermissionFlags", packageName ) @@ -1248,8 +1284,9 @@ class PermissionService( val androidPackage = packageState.androidPackage ?: return@forEach androidPackage.requestedPermissions.forEach { permissionName -> updatePermissionFlags( - packageState.appId, userId, permissionName, flagMask, flagValues, - canUpdateSystemFlags, reportErrorForUnknownPermission = false, + packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT, + flagMask, flagValues, canUpdateSystemFlags, + reportErrorForUnknownPermission = false, isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName ) } @@ -1264,6 +1301,7 @@ class PermissionService( appId: Int, userId: Int, permissionName: String, + deviceId: Int, flagMask: Int, flagValues: Int, canUpdateSystemFlags: Boolean, @@ -1298,7 +1336,7 @@ class PermissionService( return } - val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) } + val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (!isPermissionRequested && oldFlags == 0) { Slog.w( LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" + @@ -1308,7 +1346,7 @@ class PermissionService( } val newFlags = PermissionFlags.updateFlags(permission, oldFlags, flagMask, flagValues) - with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) } + setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) } override fun getAllowlistedRestrictedPermissions( @@ -1365,6 +1403,63 @@ class PermissionService( ) } + private fun GetStateScope.getPermissionFlagsWithPolicy( + appId: Int, + userId: Int, + permissionName: String, + deviceId: Int, + ): Int { + return if (deviceId == Context.DEVICE_ID_DEFAULT) { + with(policy) { getPermissionFlags(appId, userId, permissionName) } + } else { + val virtualDeviceManagerInternal = virtualDeviceManagerInternal + if (virtualDeviceManagerInternal == null) { + Slog.e(LOG_TAG, "Virtual device manager service is not available.") + return 0 + } + val persistentDeviceId = + virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + if (persistentDeviceId != null) { + with(devicePolicy) { + getPermissionFlags(appId, persistentDeviceId, userId, permissionName) + } + } else { + Slog.e(LOG_TAG, "Invalid device ID $deviceId.") + 0 + } + } + } + + private fun MutateStateScope.setPermissionFlagsWithPolicy( + appId: Int, + userId: Int, + permissionName: String, + deviceId: Int, + flags: Int + ): Boolean { + return if (deviceId == Context.DEVICE_ID_DEFAULT) { + with(policy) { + setPermissionFlags(appId, userId, permissionName, flags) + } + } else { + val virtualDeviceManagerInternal = virtualDeviceManagerInternal + if (virtualDeviceManagerInternal == null) { + Slog.e(LOG_TAG, "Virtual device manager service is not available.") + return false + } + val persistentDeviceId = + virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + if (persistentDeviceId != null) { + with(devicePolicy) { + setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags) + } + } else { + Slog.e(LOG_TAG, "Invalid device ID $deviceId.") + false + } + } + } + /** * This method does not enforce checks on the caller, should only be called after * required checks. @@ -1539,8 +1634,7 @@ class PermissionService( ) { service.mutateState { with(policy) { - val permissionsFlags = - getUidPermissionFlags(appId, userId) ?: return@mutateState + val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState val permissions = getPermissions() androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission -> @@ -1661,8 +1755,6 @@ class PermissionService( ) } - - override fun getAppOpPermissionPackages(permissionName: String): Array<String> { requireNotNull(permissionName) { "permissionName cannot be null" } val packageNames = ArraySet<String>() @@ -1879,7 +1971,7 @@ class PermissionService( println("Permissions:") withIndent { userState.appIdPermissionFlags[appId]?.forEachIndexed { - _, permissionName, flags -> + _, permissionName, flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) println( "$permissionName: granted=$isGranted, flags=" + @@ -1888,6 +1980,20 @@ class PermissionService( } } + userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { + _, deviceId, devicePermissionFlags -> + println("Permissions (Device $deviceId):") + withIndent { + devicePermissionFlags.forEachIndexed { _, permissionName, flags -> + val isGranted = PermissionFlags.isPermissionGranted(flags) + println( + "$permissionName: granted=$isGranted, flags=" + + PermissionFlags.toString(flags) + ) + } + } + } + println("App ops:") withIndent { userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode -> @@ -2003,6 +2109,8 @@ class PermissionService( override fun onSystemReady() { service.onSystemReady() + virtualDeviceManagerInternal = + LocalServices.getService(VirtualDeviceManagerInternal::class.java) permissionControllerManager = PermissionControllerManager( context, PermissionThread.getHandler() ) @@ -2412,7 +2520,7 @@ class PermissionService( } private fun isAppBackupAndRestoreRunning(uid: Int): Boolean { - if (checkUidPermission(uid, Manifest.permission.BACKUP) != + if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != PackageManager.PERMISSION_GRANTED) { return false } diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java index b7a0cf389396..e33ca7775e22 100644 --- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java +++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java @@ -138,14 +138,6 @@ public class CrossUserPackageVisibilityTests { } @Test - public void testGetUserMinAspectRatio_withCrossUserId() { - final int crossUserId = UserHandle.myUserId() + 1; - assertThrows(SecurityException.class, - () -> mIPackageManager.getUserMinAspectRatio( - mInstrumentation.getContext().getPackageName(), crossUserId)); - } - - @Test public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception { final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName()); assertThrows(IllegalArgumentException.class, diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java index 2d93120681ec..007c0db1b731 100644 --- a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java +++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java @@ -21,12 +21,14 @@ import static com.google.common.truth.Truth8.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; import android.hardware.security.keymint.DeviceInfo; import android.hardware.security.keymint.IRemotelyProvisionedComponent; import android.hardware.security.keymint.MacedPublicKey; @@ -34,28 +36,35 @@ import android.hardware.security.keymint.ProtectedData; import android.hardware.security.keymint.RpcHardwareInfo; import android.os.Binder; import android.os.FileUtils; +import android.os.OutcomeReceiver; +import android.os.Process; +import android.security.rkp.service.RegistrationProxy; +import android.security.rkp.service.RemotelyProvisionedKey; +import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.util.Arrays; import java.util.Base64; import java.util.Map; +import java.util.concurrent.Executor; @RunWith(AndroidJUnit4.class) public class RemoteProvisioningShellCommandTest { - private static class Injector extends RemoteProvisioningShellCommand.Injector { + private Context mContext; - private final Map<String, IRemotelyProvisionedComponent> mIrpcs; + private static class Injector extends RemoteProvisioningShellCommand.Injector { - Injector(Map irpcs) { - mIrpcs = irpcs; - } + Map<String, IRemotelyProvisionedComponent> mIrpcs; + Map<String, RegistrationProxy> mRegistrationProxies; @Override String[] getIrpcNames() { @@ -70,6 +79,12 @@ public class RemoteProvisioningShellCommandTest { } return irpc; } + + @Override + RegistrationProxy getRegistrationProxy( + Context context, int callerUid, String name, Executor executor) { + return mRegistrationProxies.get(name); + } } private static class CommandResult { @@ -111,10 +126,17 @@ public class RemoteProvisioningShellCommandTest { code, FileUtils.readTextFile(out, 0, null), FileUtils.readTextFile(err, 0, null)); } + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + } + @Test public void list_zeroInstances() throws Exception { + Injector injector = new Injector(); + injector.mIrpcs = Map.of(); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of())); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); @@ -124,8 +146,10 @@ public class RemoteProvisioningShellCommandTest { @Test public void list_oneInstances() throws Exception { + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", mock(IRemotelyProvisionedComponent.class)); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", mock(IRemotelyProvisionedComponent.class)))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); @@ -134,10 +158,12 @@ public class RemoteProvisioningShellCommandTest { @Test public void list_twoInstances() throws Exception { + Injector injector = new Injector(); + injector.mIrpcs = Map.of( + "default", mock(IRemotelyProvisionedComponent.class), + "strongbox", mock(IRemotelyProvisionedComponent.class)); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of( - "default", mock(IRemotelyProvisionedComponent.class), - "strongbox", mock(IRemotelyProvisionedComponent.class)))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"list"}); assertThat(res.getErr()).isEmpty(); assertThat(res.getCode()).isEqualTo(0); @@ -158,8 +184,10 @@ public class RemoteProvisioningShellCommandTest { }).when(defaultMock).generateCertificateRequest( anyBoolean(), any(), any(), any(), any(), any()); + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", defaultMock))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] { "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); verify(defaultMock).generateCertificateRequest( @@ -189,8 +217,10 @@ public class RemoteProvisioningShellCommandTest { }).when(defaultMock).generateCertificateRequest( anyBoolean(), any(), any(), any(), any(), any()); + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", defaultMock))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] { "csr", "--challenge", "dGVzdHRlc3R0ZXN0dGVzdA==", "default"}); verify(defaultMock).generateCertificateRequest( @@ -215,8 +245,10 @@ public class RemoteProvisioningShellCommandTest { when(defaultMock.generateCertificateRequestV2(any(), any())) .thenReturn(new byte[] {0x68, 0x65, 0x6c, 0x6c, 0x6f}); + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", defaultMock))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"csr", "default"}); verify(defaultMock).generateCertificateRequestV2(new MacedPublicKey[0], new byte[0]); assertThat(res.getErr()).isEmpty(); @@ -233,8 +265,10 @@ public class RemoteProvisioningShellCommandTest { when(defaultMock.generateCertificateRequestV2(any(), any())) .thenReturn(new byte[] {0x68, 0x69}); + Injector injector = new Injector(); + injector.mIrpcs = Map.of("default", defaultMock); RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( - new Injector(Map.of("default", defaultMock))); + mContext, Process.SHELL_UID, injector); CommandResult res = exec(cmd, new String[] {"csr", "--challenge", "dHJpYWw=", "default"}); verify(defaultMock).generateCertificateRequestV2( new MacedPublicKey[0], new byte[] {0x74, 0x72, 0x69, 0x61, 0x6c}); @@ -242,4 +276,82 @@ public class RemoteProvisioningShellCommandTest { assertThat(res.getCode()).isEqualTo(0); assertThat(res.getOut()).isEqualTo("aGk=\n"); } + + @Test + public void certify_sameOrderAsReceived() throws Exception { + String cert1 = "MIIBqDCCAU2gAwIBAgIUI3FFU7xZno/2Xf/wZzKKquP0ov0wCgYIKoZIzj0EAwIw\n" + + "KTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MB4XDTIz\n" + + "MDgyMjE5MzgxMFoXDTMzMDgxOTE5MzgxMFowKTELMAkGA1UEBhMCVVMxCzAJBgNV\n" + + "BAgMAkNBMQ0wCwYDVQQKDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n" + + "czOpG6NKOdDjV/yrKjuy0q0jEJvsVLGgTeY+vyKRBJS59OhyRWG6n3aza21bNg5d\n" + + "WE9ruz+bcT0IP4kDbiS0y6NTMFEwHQYDVR0OBBYEFHYfJxCUipNI7qRqvczcWsOb\n" + + "FIDPMB8GA1UdIwQYMBaAFHYfJxCUipNI7qRqvczcWsObFIDPMA8GA1UdEwEB/wQF\n" + + "MAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKm/kpJwlnWkjoLCAddBiSnxbT4EfJIK\n" + + "H0j58tg5VazHAiEAnS/kRzU9AbstOZyD7el/ws3gLXkbUNey3pLFutBWsSU=\n"; + String cert2 = "MIIBpjCCAU2gAwIBAgIUdSzfZzeGr+h70JPO7Sxwdkw99iMwCgYIKoZIzj0EAwIw\n" + + "KTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQKDARUZXN0MB4XDTIz\n" + + "MDgyMjIwMTcyMFoXDTMzMDgxOTIwMTcyMFowKTELMAkGA1UEBhMCVVMxCzAJBgNV\n" + + "BAgMAkNBMQ0wCwYDVQQKDARUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE\n" + + "voGJi4DxuqH8rzPV6Eq0OVULc0xFzaM0500VBqiQEB7Qt0Ktk2d+3bUrFAb3SZV4\n" + + "6TIdb7SkynvaDtr0x45Ng6NTMFEwHQYDVR0OBBYEFMeGjvGV0ADPBJk5/FPoW9HQ\n" + + "uTc6MB8GA1UdIwQYMBaAFMeGjvGV0ADPBJk5/FPoW9HQuTc6MA8GA1UdEwEB/wQF\n" + + "MAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgd1gu7iiNOQXaQUn5BT3WwWR0Yk78ndWt\n" + + "ew7tRiTOhFcCIFURi6WcNH0oWa6IbwBSMC9aZlo98Fbg+dTwhLAAw+PW\n"; + byte[] cert1Bytes = Base64.getDecoder().decode(cert1.replaceAll("\\s+", "")); + byte[] cert2Bytes = Base64.getDecoder().decode(cert2.replaceAll("\\s+", "")); + byte[] certChain = Arrays.copyOf(cert1Bytes, cert1Bytes.length + cert2Bytes.length); + System.arraycopy(cert2Bytes, 0, certChain, cert1Bytes.length, cert2Bytes.length); + RemotelyProvisionedKey keyMock = mock(RemotelyProvisionedKey.class); + when(keyMock.getEncodedCertChain()).thenReturn(certChain); + RegistrationProxy defaultMock = mock(RegistrationProxy.class); + doAnswer(invocation -> { + ((OutcomeReceiver<RemotelyProvisionedKey, Exception>) invocation.getArgument(3)) + .onResult(keyMock); + return null; + }).when(defaultMock).getKeyAsync(anyInt(), any(), any(), any()); + + Injector injector = new Injector(); + injector.mRegistrationProxies = Map.of("default", defaultMock); + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + mContext, Process.SHELL_UID, injector); + CommandResult res = exec(cmd, new String[] {"certify", "default"}); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(res.getOut()).isEqualTo( + "-----BEGIN CERTIFICATE-----\n" + cert1 + "-----END CERTIFICATE-----\n" + + "-----BEGIN CERTIFICATE-----\n" + cert2 + "-----END CERTIFICATE-----\n"); + } + + @Test + public void certify_noBlankLineBeforeTrailer() throws Exception { + String cert = "MIIB2zCCAYGgAwIBAgIRAOpN7Em1k7gaqLAB2dzXUTYwCgYIKoZIzj0EAwIwKTET\n" + + "MBEGA1UEChMKR29vZ2xlIExMQzESMBAGA1UEAxMJRHJvaWQgQ0EzMB4XDTIzMDgx\n" + + "ODIzMzI1MloXDTIzMDkyMTIzMzI1MlowOTEMMAoGA1UEChMDVEVFMSkwJwYDVQQD\n" + + "EyBlYTRkZWM0OWI1OTNiODFhYThiMDAxZDlkY2Q3NTEzNjBZMBMGByqGSM49AgEG\n" + + "CCqGSM49AwEHA0IABHM/cKZblmlw8bdGbDXnX+ZiLiGjSjaLHXYOoHDrVArAMXUi\n" + + "L6brhcUPaqSGcVLcfFZbaFMOxXW6TsGdQiwJ0iyjejB4MB0GA1UdDgQWBBTYzft+\n" + + "X32TH/Hh+ngwQF6aPhnfXDAfBgNVHSMEGDAWgBQT4JObI9mzNNW2FRsHRcw4zVn2\n" + + "8jAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDAVBgorBgEEAdZ5AgEe\n" + + "BAehARoABAAAMAoGCCqGSM49BAMCA0gAMEUCIDc0OR7CzIYw0myTr0y/Brl1nZyk\n" + + "eGSQp615WpTwYhwxAiEApM10gSIKBIo7Z4/FNzkuiz1zZwW9+Dcqisqxkfe6icQ=\n"; + byte[] certBytes = Base64.getDecoder().decode(cert.replaceAll("\\s+", "")); + RemotelyProvisionedKey keyMock = mock(RemotelyProvisionedKey.class); + when(keyMock.getEncodedCertChain()).thenReturn(certBytes); + RegistrationProxy defaultMock = mock(RegistrationProxy.class); + doAnswer(invocation -> { + ((OutcomeReceiver<RemotelyProvisionedKey, Exception>) invocation.getArgument(3)) + .onResult(keyMock); + return null; + }).when(defaultMock).getKeyAsync(anyInt(), any(), any(), any()); + + Injector injector = new Injector(); + injector.mRegistrationProxies = Map.of("strongbox", defaultMock); + RemoteProvisioningShellCommand cmd = new RemoteProvisioningShellCommand( + mContext, Process.SHELL_UID, injector); + CommandResult res = exec(cmd, new String[] {"certify", "strongbox"}); + assertThat(res.getErr()).isEmpty(); + assertThat(res.getCode()).isEqualTo(0); + assertThat(res.getOut()).isEqualTo( + "-----BEGIN CERTIFICATE-----\n" + cert + "-----END CERTIFICATE-----\n"); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index a6acd60f3bd7..d16c37a4c07a 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -63,8 +63,10 @@ public class AutomaticBrightnessControllerTest { private static final float BRIGHTNESS_MAX_FLOAT = 1.0f; private static final int LIGHT_SENSOR_RATE = 20; private static final int INITIAL_LIGHT_SENSOR_RATE = 20; - private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 0; - private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 0; + private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG = 2000; + private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000; + private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000; + private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000; private static final float DOZE_SCALE_FACTOR = 0.0f; private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; @@ -96,8 +98,9 @@ public class AutomaticBrightnessControllerTest { mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); mContext = InstrumentationRegistry.getContext(); - mController = setupController(mLightSensor, BrightnessMappingStrategy.NO_USER_LUX, - BrightnessMappingStrategy.NO_USER_BRIGHTNESS); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ false, + /* useHorizon= */ true); } @After @@ -109,12 +112,12 @@ public class AutomaticBrightnessControllerTest { } } - private AutomaticBrightnessController setupController(Sensor lightSensor, float userLux, - float userBrightness) { + private void setupController(float userLux, float userBrightness, boolean applyDebounce, + boolean useHorizon) { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); - AutomaticBrightnessController controller = new AutomaticBrightnessController( + mController = new AutomaticBrightnessController( new AutomaticBrightnessController.Injector() { @Override public Handler getBackgroundThreadHandler() { @@ -127,16 +130,19 @@ public class AutomaticBrightnessControllerTest { } }, // pass in test looper instead, pass in offsettable clock - () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor, + () -> { }, mTestLooper.getLooper(), mSensorManager, mLightSensor, mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT, BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, - INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, - DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, + INITIAL_LIGHT_SENSOR_RATE, applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG : 0, + applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG : 0, + applyDebounce ? BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0, + applyDebounce ? DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE : 0, + RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mAmbientBrightnessThresholdsIdle, mScreenBrightnessThresholdsIdle, mContext, mBrightnessRangeController, mBrightnessThrottler, - mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT, - AMBIENT_LIGHT_HORIZON_LONG, userLux, userBrightness + mIdleBrightnessMappingStrategy, useHorizon ? AMBIENT_LIGHT_HORIZON_SHORT : 1, + useHorizon ? AMBIENT_LIGHT_HORIZON_LONG : 10000, userLux, userBrightness ); when(mBrightnessRangeController.getCurrentBrightnessMax()).thenReturn( @@ -149,12 +155,10 @@ public class AutomaticBrightnessControllerTest { // Configure the brightness controller and grab an instance of the sensor listener, // through which we can deliver fake (for test) sensor values. - controller.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, + mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, 0 /* brightness= */, false /* userChangedBrightness= */, 0 /* adjustment= */, false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, /* shouldResetShortTermModel= */ true); - - return controller; } @Test @@ -536,8 +540,10 @@ public class AutomaticBrightnessControllerTest { // Now let's do the same for idle mode mController.switchToIdleMode(); // Called once for init, and once when switching, - // setAmbientLux() is called twice and once in updateAutoBrightness() - verify(mBrightnessMappingStrategy, times(5)).isForIdleMode(); + // setAmbientLux() is called twice and once in updateAutoBrightness(), + // nextAmbientLightBrighteningTransition() and nextAmbientLightDarkeningTransition() are + // called twice each. + verify(mBrightnessMappingStrategy, times(9)).isForIdleMode(); // Called when switching. verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout(); verify(mBrightnessMappingStrategy, times(1)).getUserBrightness(); @@ -838,7 +844,158 @@ public class AutomaticBrightnessControllerTest { float userLux = 1000; float userBrightness = 0.3f; - setupController(mLightSensor, userLux, userBrightness); + setupController(userLux, userBrightness, /* applyDebounce= */ true, + /* useHorizon= */ false); verify(mBrightnessMappingStrategy).addUserDataPoint(userLux, userBrightness); } + + @Test + public void testBrighteningLightDebounce() throws Exception { + clearInvocations(mSensorManager); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1000 + // Lux isn't steady yet + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux is steady now + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testDarkeningLightDebounce() throws Exception { + clearInvocations(mSensorManager); + when(mAmbientBrightnessThresholds.getBrighteningThreshold(anyFloat())) + .thenReturn(10000f); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(anyFloat())) + .thenReturn(10000f); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // Lux isn't steady yet + mClock.fastForward(2000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 4500 + // Lux is steady now + mClock.fastForward(2000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testBrighteningLightDebounceIdle() throws Exception { + clearInvocations(mSensorManager); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 500 + // Lux isn't steady yet + mClock.fastForward(500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + + // t = 1500 + // Lux is steady now + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + } + + @Test + public void testDarkeningLightDebounceIdle() throws Exception { + clearInvocations(mSensorManager); + when(mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(anyFloat())) + .thenReturn(10000f); + when(mAmbientBrightnessThresholdsIdle.getDarkeningThreshold(anyFloat())) + .thenReturn(10000f); + setupController(BrightnessMappingStrategy.NO_USER_LUX, + BrightnessMappingStrategy.NO_USER_BRIGHTNESS, /* applyDebounce= */ true, + /* useHorizon= */ false); + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // t = 0 + // Initial lux + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1200)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 1000 + // Lux isn't steady yet + mClock.fastForward(1000); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(1200, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // Lux is steady now + mClock.fastForward(1500); + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500)); + assertEquals(500, mController.getAmbientLux(), EPSILON); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java index 53d8de0c2bbb..3c3ef9ae166e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java @@ -25,12 +25,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.display.DisplayManagerInternal; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; -import com.android.server.LocalServices; - -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,25 +46,13 @@ public class ColorFadeTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock); mContext = getInstrumentation().getTargetContext(); } - @After - public void tearDown() { - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - } - @Test public void testPrepareColorFadeForInvalidDisplay() { when(mDisplayManagerInternalMock.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(null); - ColorFade colorFade = new ColorFade(DISPLAY_ID); + ColorFade colorFade = new ColorFade(DISPLAY_ID, mDisplayManagerInternalMock); assertFalse(colorFade.prepare(mContext, ColorFade.MODE_FADE)); } - - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index d9338a9b12bf..82d00a6fcbca 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -112,8 +112,6 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(mDisplayDeviceConfig.getBrightness(), BRIGHTNESS, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getNits(), NITS, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA); - assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000); - assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000); assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA); assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new @@ -413,6 +411,17 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(new int[]{ -1, 10, 20, 30, 40 }, mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux()); + + // Low/High zone thermal maps + assertEquals(new SurfaceControl.RefreshRateRange(30, 40), + mDisplayDeviceConfig.getLowBlockingZoneThermalMap() + .get(Temperature.THROTTLING_CRITICAL)); + assertEquals(new SurfaceControl.RefreshRateRange(40, 60), + mDisplayDeviceConfig.getHighBlockingZoneThermalMap() + .get(Temperature.THROTTLING_EMERGENCY)); + + // Todo: Add asserts for DensityMapping, + // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. } @Test @@ -540,6 +549,16 @@ public final class DisplayDeviceConfigTest { assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name); } + @Test + public void testLightDebounceFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000); + assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000); + assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounceIdle(), 2500); + assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounceIdle(), 1500); + } + private String getValidLuxThrottling() { return "<luxThrottling>\n" + " <brightnessLimitMap>\n" @@ -632,6 +651,24 @@ public final class DisplayDeviceConfigTest { + " </refreshRateRange>\n" + " </refreshRateThrottlingPoint>\n" + "</refreshRateThrottlingMap>\n" + + "<refreshRateThrottlingMap id=\"thermalLow\">\n" + + " <refreshRateThrottlingPoint>\n" + + " <thermalStatus>critical</thermalStatus>\n" + + " <refreshRateRange>\n" + + " <minimum>30</minimum>\n" + + " <maximum>40</maximum>\n" + + " </refreshRateRange>\n" + + " </refreshRateThrottlingPoint>\n" + + "</refreshRateThrottlingMap>\n" + + "<refreshRateThrottlingMap id=\"thermalHigh\">\n" + + " <refreshRateThrottlingPoint>\n" + + " <thermalStatus>emergency</thermalStatus>\n" + + " <refreshRateRange>\n" + + " <minimum>40</minimum>\n" + + " <maximum>60</maximum>\n" + + " </refreshRateRange>\n" + + " </refreshRateThrottlingPoint>\n" + + "</refreshRateThrottlingMap>\n" + "<refreshRateThrottlingMap id=\"test\">\n" + " <refreshRateThrottlingPoint>\n" + " <thermalStatus>emergency</thermalStatus>\n" @@ -704,6 +741,12 @@ public final class DisplayDeviceConfigTest { + "<autoBrightness>\n" + "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n" + "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n" + + "<brighteningLightDebounceIdleMillis>" + + "2500" + + "</brighteningLightDebounceIdleMillis>\n" + + "<darkeningLightDebounceIdleMillis>" + + "1500" + + "</darkeningLightDebounceIdleMillis>\n" + "<displayBrightnessMapping>\n" + "<displayBrightnessPoint>\n" + "<lux>50</lux>\n" @@ -969,6 +1012,8 @@ public final class DisplayDeviceConfigTest { + "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n" + "<lowerBlockingZoneConfigs>\n" + "<defaultRefreshRate>75</defaultRefreshRate>\n" + + "<refreshRateThermalThrottlingId>thermalLow" + + "</refreshRateThermalThrottlingId>\n" + "<blockingZoneThreshold>\n" + "<displayBrightnessPoint>\n" + "<lux>50</lux>\n" @@ -990,6 +1035,8 @@ public final class DisplayDeviceConfigTest { + "</lowerBlockingZoneConfigs>\n" + "<higherBlockingZoneConfigs>\n" + "<defaultRefreshRate>90</defaultRefreshRate>\n" + + "<refreshRateThermalThrottlingId>thermalHigh" + + "</refreshRateThermalThrottlingId>\n" + "<blockingZoneThreshold>\n" + "<displayBrightnessPoint>\n" + "<lux>70</lux>\n" diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index 4c3d80a6422b..4cbdd09319cc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -1061,6 +1061,8 @@ public final class DisplayPowerController2Test { anyInt(), anyLong(), anyLong(), + anyLong(), + anyLong(), anyBoolean(), any(HysteresisLevels.class), any(HysteresisLevels.class), @@ -1350,6 +1352,7 @@ public final class DisplayPowerController2Test { int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 01e49f2e207d..68bbcbc1df51 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -63,8 +63,8 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.modules.utils.testing.ExtendedMockitoRule; -import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; @@ -74,7 +74,6 @@ import com.android.server.display.whitebalance.DisplayWhiteBalanceController; import com.android.server.policy.WindowManagerPolicy; import com.android.server.testutils.OffsettableClock; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -140,6 +139,9 @@ public final class DisplayPowerControllerTest { .spyStatic(BatteryStatsService.class) .build(); + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + @Before public void setUp() throws Exception { mClock = new OffsettableClock.Stopped(); @@ -153,9 +155,10 @@ public final class DisplayPowerControllerTest { Settings.System.putFloatForUser(mContext.getContentResolver(), Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, UserHandle.USER_CURRENT); - addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock); - addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class, - mCdsiMock); + mLocalServiceKeeperRule.overrideLocalService( + WindowManagerPolicy.class, mWindowManagerPolicyMock); + mLocalServiceKeeperRule.overrideLocalService( + ColorDisplayService.ColorDisplayServiceInternal.class, mCdsiMock); mContext.addMockSystemService(PowerManager.class, mPowerManagerMock); @@ -167,12 +170,6 @@ public final class DisplayPowerControllerTest { mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); } - @After - public void tearDown() { - LocalServices.removeServiceForTest(WindowManagerPolicy.class); - LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); - } - @Test public void testReleaseProxSuspendBlockersOnExit() throws Exception { when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); @@ -1071,6 +1068,8 @@ public final class DisplayPowerControllerTest { anyInt(), anyLong(), anyLong(), + anyLong(), + anyLong(), anyBoolean(), any(HysteresisLevels.class), any(HysteresisLevels.class), @@ -1124,14 +1123,6 @@ public final class DisplayPowerControllerTest { verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true); } - /** - * Creates a mock and registers it to {@link LocalServices}. - */ - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); @@ -1330,6 +1321,7 @@ public final class DisplayPowerControllerTest { int lightSensorWarmUpTime, float brightnessMin, float brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, + long brighteningLightDebounceConfigIdle, long darkeningLightDebounceConfigIdle, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index b088dbfcf13b..168eefcb4f0c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -39,6 +39,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -1093,6 +1094,196 @@ public class DisplayModeDirectorTest { } @Test + public void testLockFpsForHighZoneWithThermalCondition() throws Exception { + // First, configure brightness zones or DMD won't register for sensor data. + final FakeDeviceConfig config = mInjector.getDeviceConfig(); + config.setHighDisplayBrightnessThresholds(new int[] { 200 }); + config.setHighAmbientBrightnessThresholds(new int[] { 8000 }); + + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 120.f}, 0); + setPeakRefreshRate(120 /*fps*/); + director.getSettingsObserver().setDefaultRefreshRate(120); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + // Set the thresholds for High Zone + DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class); + when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(90); + when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[] { 200 }); + when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[] { 8000 }); + when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90); + when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[] {}); + when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[] {}); + + // Set the thermal condition for refresh rate range + when(ddcMock.getHighBlockingZoneThermalMap()).thenReturn( + new SparseArray<RefreshRateRange>() {{ + put(Temperature.THROTTLING_CRITICAL, new RefreshRateRange(60, 60)); + }} + ); + director.defaultDisplayDeviceUpdated(ddcMock); // set the ddc + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + // Get the display listener so that we can send it new brightness events + ArgumentCaptor<DisplayListener> displayListenerCaptor = + ArgumentCaptor.forClass(DisplayListener.class); + verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + any(Handler.class), + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + DisplayListener displayListener = displayListenerCaptor.getValue(); + + // Get the sensor listener so that we can give it new light sensor events + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1))) + .registerListener( + listenerCaptor.capture(), + eq(lightSensor), + anyInt(), + any(Handler.class)); + SensorEventListener sensorListener = listenerCaptor.getValue(); + + // Get the thermal listener so that we can give it new thermal conditions + ArgumentCaptor<IThermalEventListener> thermalListenerCaptor = + ArgumentCaptor.forClass(IThermalEventListener.class); + verify(mInjector, atLeastOnce()).registerThermalServiceListener( + thermalListenerCaptor.capture()); + List<IThermalEventListener> thermalListeners = thermalListenerCaptor.getAllValues(); + + setBrightness(100, 100, displayListener); + // Sensor reads 2000 lux, + sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000)); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertThat(vote).isNull(); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNull(); + + // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this + // parameter to the necessary threshold + setBrightness(255, 255, displayListener); + // Sensor reads 9000 lux, + sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000)); + + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); + + // Set critical and check new refresh rate + Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL); + for (var listener : thermalListeners) { + listener.notifyThrottling(temp); + } + + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); + } + + @Test + public void testLockFpsForLowZoneWithThermalCondition() throws Exception { + // First, configure brightness zones or DMD won't register for sensor data. + final FakeDeviceConfig config = mInjector.getDeviceConfig(); + config.setHighDisplayBrightnessThresholds(new int[] { 200 }); + config.setHighAmbientBrightnessThresholds(new int[] { 8000 }); + + DisplayModeDirector director = + createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 120.f}, 0); + setPeakRefreshRate(120 /*fps*/); + director.getSettingsObserver().setDefaultRefreshRate(120); + director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON); + + // Set the thresholds for Low Zone + DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class); + when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90); + when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[] { 200 }); + when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[] { 8000 }); + when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90); + when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[] { 10 }); + when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[] { 10 }); + + // Set the thermal condition for refresh rate range + when(ddcMock.getLowBlockingZoneThermalMap()).thenReturn( + new SparseArray<RefreshRateRange>() {{ + put(Temperature.THROTTLING_CRITICAL, new RefreshRateRange(60, 60)); + }} + ); + director.defaultDisplayDeviceUpdated(ddcMock); // set the ddc + + Sensor lightSensor = createLightSensor(); + SensorManager sensorManager = createMockSensorManager(lightSensor); + director.start(sensorManager); + + // Get the display listener so that we can send it new brightness events + ArgumentCaptor<DisplayListener> displayListenerCaptor = + ArgumentCaptor.forClass(DisplayListener.class); + verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + any(Handler.class), + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); + DisplayListener displayListener = displayListenerCaptor.getValue(); + + // Get the sensor listener so that we can give it new light sensor events + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1))) + .registerListener( + listenerCaptor.capture(), + eq(lightSensor), + anyInt(), + any(Handler.class)); + SensorEventListener sensorListener = listenerCaptor.getValue(); + + // Get the thermal listener so that we can give it new thermal conditions + ArgumentCaptor<IThermalEventListener> thermalListenerCaptor = + ArgumentCaptor.forClass(IThermalEventListener.class); + verify(mInjector, atLeastOnce()).registerThermalServiceListener( + thermalListenerCaptor.capture()); + List<IThermalEventListener> thermalListeners = thermalListenerCaptor.getAllValues(); + + setBrightness(100, 100, displayListener); + // Sensor reads 2000 lux, + sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000)); + + Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertThat(vote).isNull(); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNull(); + + // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this + // parameter to the necessary threshold + setBrightness(5, 5, displayListener); + // Sensor reads 9 lux, + sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9)); + + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); + + // Set critical and check new refresh rate + Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL); + for (var listener : thermalListeners) { + listener.notifyThrottling(temp); + } + + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE); + assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/); + vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH); + assertThat(vote).isNotNull(); + assertThat(vote.disableRefreshRateSwitching).isTrue(); + } + + @Test public void testSensorRegistration() { // First, configure brightness zones or DMD won't register for sensor data. final FakeDeviceConfig config = mInjector.getDeviceConfig(); @@ -2907,6 +3098,10 @@ public class DisplayModeDirectorTest { } @Override + public void unregisterThermalServiceListener(IThermalEventListener listener) { + } + + @Override public boolean supportsFrameRateOverride() { return true; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java index 183a84de0eb0..f9fbf1bd5085 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java @@ -38,7 +38,7 @@ import android.util.TypedValue; import androidx.test.InstrumentationRegistry; import com.android.internal.R; -import com.android.server.LocalServices; +import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.server.display.TestUtils; import com.android.server.display.color.ColorDisplayService; import com.android.server.display.utils.AmbientFilter; @@ -47,6 +47,7 @@ import com.android.server.display.utils.AmbientFilterStubber; import com.google.common.collect.ImmutableList; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -90,6 +91,9 @@ public final class AmbientLuxTest { @Mock private TypedArray mStrongDisplayColorTemperatures; @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock; + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -156,8 +160,9 @@ public final class AmbientLuxTest { R.array.config_displayWhiteBalanceHighLightAmbientBiasesStrong)) .thenReturn(mHighLightBiasesStrong); mockThrottler(); - LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); - LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class, + + mLocalServiceKeeperRule.overrideLocalService( + ColorDisplayService.ColorDisplayServiceInternal.class, mColorDisplayServiceInternalMock); } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java new file mode 100644 index 000000000000..8e17b3a58769 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java @@ -0,0 +1,84 @@ +/* + * 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.server.backup.utils; + +import static org.junit.Assert.assertTrue; + +import android.app.backup.BackupManagerMonitor; +import android.os.Bundle; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; + +public class BackupManagerMonitorDumpsysUtilsTest { + private File mTempFile; + private TestBackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtils; + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + @Before + public void setUp() throws Exception { + mTempFile = tmp.newFile("testbmmevents.txt"); + mBackupManagerMonitorDumpsysUtils = new TestBackupManagerMonitorDumpsysUtils(); + } + + + @Test + public void parseBackupManagerMonitorEventForDumpsys_bundleIsNull_noLogsWrittenToFile() + throws Exception { + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(null); + + assertTrue(mTempFile.length() == 0); + + } + + @Test + public void parseBackupManagerMonitorEventForDumpsys_missingID_noLogsWrittenToFile() + throws Exception { + Bundle event = new Bundle(); + event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, 1); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + + assertTrue(mTempFile.length() == 0); + } + + @Test + public void parseBackupManagerMonitorEventForDumpsys_missingCategory_noLogsWrittenToFile() + throws Exception { + Bundle event = new Bundle(); + event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, 1); + mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event); + + assertTrue(mTempFile.length() == 0); + } + + private class TestBackupManagerMonitorDumpsysUtils + extends BackupManagerMonitorDumpsysUtils { + TestBackupManagerMonitorDumpsysUtils() { + super(); + } + + @Override + public File getBMMEventsFile() { + return mTempFile; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java index 9d9b03fa6862..3af2932ee937 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java @@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -63,26 +64,40 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class BackupManagerMonitorEventSenderTest { @Mock private IBackupManagerMonitor mMonitorMock; + @Mock private BackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtilsMock; private BackupManagerMonitorEventSender mBackupManagerMonitorEventSender; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(mMonitorMock); + mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(mMonitorMock, + mBackupManagerMonitorDumpsysUtilsMock); } @Test - public void monitorEvent_monitorIsNull_returnsNull() throws Exception { - mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(/* monitor */ null); + public void monitorEvent_monitorIsNull_sendBundleToDumpsys() throws Exception { + Bundle extras = new Bundle(); + extras.putInt(EXTRA_LOG_OPERATION_TYPE, OperationType.RESTORE); + mBackupManagerMonitorEventSender.setMonitor(null); + mBackupManagerMonitorEventSender.monitorEvent(0, null, 0, extras); + IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor(); + + verify(mBackupManagerMonitorDumpsysUtilsMock).parseBackupManagerMonitorRestoreEventForDumpsys(any( + Bundle.class)); + } + + @Test + public void monitorEvent_monitorIsNull_doNotCallOnEvent() throws Exception { + mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(null); mBackupManagerMonitorEventSender.monitorEvent(0, null, 0, null); IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor(); - assertThat(monitor).isNull(); + verify(mMonitorMock, never()).onEvent(any(Bundle.class)); } @Test - public void monitorEvent_monitorOnEventThrows_returnsNull() throws Exception { + public void monitorEvent_monitorOnEventThrows_setsMonitorToNull() throws Exception { doThrow(new RemoteException()).when(mMonitorMock).onEvent(any(Bundle.class)); mBackupManagerMonitorEventSender.monitorEvent(0, null, 0, null); @@ -93,6 +108,14 @@ public class BackupManagerMonitorEventSenderTest { } @Test + public void monitorEvent_extrasAreNull_doNotSendBundleToDumpsys() throws Exception { + mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, null); + + verify(mBackupManagerMonitorDumpsysUtilsMock, never()) + .parseBackupManagerMonitorRestoreEventForDumpsys(any(Bundle.class)); + } + + @Test public void monitorEvent_packageAndExtrasAreNull_fillsBundleCorrectly() throws Exception { mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, null); IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor(); @@ -162,6 +185,36 @@ public class BackupManagerMonitorEventSenderTest { } @Test + public void monitorEvent_eventOpTypeIsRestore_sendBundleToDumpsys() throws Exception { + Bundle extras = new Bundle(); + extras.putInt(EXTRA_LOG_OPERATION_TYPE, OperationType.RESTORE); + mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, extras); + + verify(mBackupManagerMonitorDumpsysUtilsMock).parseBackupManagerMonitorRestoreEventForDumpsys(any( + Bundle.class)); + } + + @Test + public void monitorEvent_eventOpTypeIsBackup_doNotSendBundleToDumpsys() throws Exception { + Bundle extras = new Bundle(); + extras.putInt(EXTRA_LOG_OPERATION_TYPE, OperationType.BACKUP); + mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, extras); + + verify(mBackupManagerMonitorDumpsysUtilsMock, never()) + .parseBackupManagerMonitorRestoreEventForDumpsys(any(Bundle.class)); + } + + @Test + public void monitorEvent_eventOpTypeIsUnknown_doNotSendBundleToDumpsys() throws Exception { + Bundle extras = new Bundle(); + extras.putInt(EXTRA_LOG_OPERATION_TYPE, OperationType.UNKNOWN); + mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, extras); + + verify(mBackupManagerMonitorDumpsysUtilsMock, never()) + .parseBackupManagerMonitorRestoreEventForDumpsys(any(Bundle.class)); + } + + @Test public void monitorAgentLoggingResults_onBackup_fillsBundleCorrectly() throws Exception { PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = "test.package"; diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 908afc861f8b..00e35ecece8b 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -226,6 +226,7 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceManagerService mVdms; private VirtualDeviceManagerInternal mLocalService; private VirtualDeviceManagerService.VirtualDeviceManagerImpl mVdm; + private VirtualDeviceLog mVirtualDeviceLog; @Mock private InputController.NativeWrapper mNativeWrapperMock; @Mock @@ -370,6 +371,7 @@ public class VirtualDeviceManagerServiceTest { mVdms = new VirtualDeviceManagerService(mContext); mLocalService = mVdms.getLocalServiceInstance(); mVdm = mVdms.new VirtualDeviceManagerImpl(); + mVirtualDeviceLog = new VirtualDeviceLog(mContext); mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1); mSensorController = mDeviceImpl.getSensorControllerForTest(); } @@ -1730,7 +1732,7 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid, VirtualDeviceParams params) { VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext, - mAssociationInfo, mVdms, new Binder(), + mAssociationInfo, mVdms, mVirtualDeviceLog, new Binder(), new AttributionSource(ownerUid, "com.android.virtualdevice.test", "virtualdevice"), virtualDeviceId, mInputController, mCameraAccessController, diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index ee3ab9744fdd..9fca513e50b9 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -149,32 +149,6 @@ public class HintManagerServiceTest { } @Test - public void testCreateHintSession_exceedsLimit() throws Exception { - HintManagerService service = createService(); - IBinder token1 = new Binder(); - IBinder token2 = new Binder(); - - for (int i = 0; i < 10; i++) { - IHintSession a = service.getBinderServiceInstance().createHintSession(token1, - SESSION_TIDS_A, DEFAULT_TARGET_DURATION); - assertNotNull(a); - } - - for (int i = 0; i < 10; i++) { - IHintSession b = service.getBinderServiceInstance().createHintSession(token2, - SESSION_TIDS_B, DEFAULT_TARGET_DURATION); - assertNotNull(b); - } - - assertThrows(IllegalStateException.class, - () -> service.getBinderServiceInstance().createHintSession(token1, SESSION_TIDS_A, - DEFAULT_TARGET_DURATION)); - assertThrows(IllegalStateException.class, - () -> service.getBinderServiceInstance().createHintSession(token2, SESSION_TIDS_B, - DEFAULT_TARGET_DURATION)); - } - - @Test public void testPauseResumeHintSession() throws Exception { HintManagerService service = createService(); IBinder token = new Binder(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 81d939f1f05f..9b745f5aaf4f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -115,6 +115,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Parcel; +import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; @@ -209,6 +210,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Mock Context mContext; @Mock ZenModeHelper mMockZenModeHelper; @Mock AppOpsManager mAppOpsManager; + @Mock ManagedServices.UserProfiles mUserProfiles; @Mock PermissionManager mPermissionManager; private NotificationManager.Policy mTestNotificationPolicy; @@ -327,10 +329,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) .thenReturn(appPermissions); + when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0})); + mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory(); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, mStatsEventBuilderFactory, false); resetZenModeHelper(); @@ -678,7 +682,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migrates() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"2\">\n" + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 @@ -743,9 +748,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getPackageUidAsUser("pkg3", USER_SYSTEM)).thenReturn(UNKNOWN_UID); @@ -822,7 +824,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -879,7 +882,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_permissionNotificationOff() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ false); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -936,7 +940,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, /* showReviewPermissionsNotification= */ true); String xml = "<ranking version=\"4\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -991,9 +996,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migration_NoUid() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); String xml = "<ranking version=\"2\">\n" + "<package name=\"something\" show_badge=\"true\">\n" @@ -1024,9 +1026,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_NoUid() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); String xml = "<ranking version=\"3\">\n" + "<package name=\"something\" show_badge=\"true\">\n" @@ -1056,9 +1055,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForNonBackup_postMigration() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); @@ -1142,9 +1138,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); @@ -1234,9 +1227,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noExternal() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false)); appPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, false)); @@ -1319,9 +1309,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); - ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false)); appPermissions.put(new Pair<>(3, "third"), new Pair<>(false, false)); @@ -1533,7 +1520,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { new FileNotFoundException("")).thenReturn(resId); mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, + mStatsEventBuilderFactory, false); NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); @@ -2477,9 +2465,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.syncChannelsBypassingDnd(); // create notification channel that can bypass dnd, but app is blocked @@ -2508,9 +2493,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.syncChannelsBypassingDnd(); // create notification channel that can bypass dnd, but app is blocked @@ -2533,9 +2515,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.syncChannelsBypassingDnd(); // create notification channel that can bypass dnd, but app is blocked @@ -2588,9 +2567,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.syncChannelsBypassingDnd(); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); @@ -2602,15 +2578,58 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start notification policy off with mAreChannelsBypassingDnd = false mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); resetZenModeHelper(); } @Test + public void syncChannelsBypassingDnd_includesProfilesOfCurrentUser() throws Exception { + when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0, 10})); + when(mPermissionHelper.hasPermission(anyInt())).thenReturn(true); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + when(mPm.getApplicationInfoAsUser(any(), anyInt(), anyInt())).thenReturn(appInfo); + + NotificationChannel withBypass = new NotificationChannel("1", "with", IMPORTANCE_DEFAULT); + withBypass.setBypassDnd(true); + NotificationChannel withoutBypass = new NotificationChannel("2", "without", + IMPORTANCE_DEFAULT); + withoutBypass.setBypassDnd(false); + mHelper.createNotificationChannel("com.example", UserHandle.getUid(0, 444), withoutBypass, + false, false, Process.SYSTEM_UID, true); + mHelper.createNotificationChannel("com.example", UserHandle.getUid(10, 444), withBypass, + false, false, Process.SYSTEM_UID, true); + + mHelper.syncChannelsBypassingDnd(); + + assertThat(mHelper.areChannelsBypassingDnd()).isTrue(); + } + + @Test + public void syncChannelsBypassingDnd_excludesOtherUsers() throws Exception { + when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0})); + when(mPermissionHelper.hasPermission(anyInt())).thenReturn(true); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + when(mPm.getApplicationInfoAsUser(any(), anyInt(), anyInt())).thenReturn(appInfo); + + NotificationChannel withBypass = new NotificationChannel("1", "with", IMPORTANCE_DEFAULT); + withBypass.setBypassDnd(true); + NotificationChannel withoutBypass = new NotificationChannel("2", "without", + IMPORTANCE_DEFAULT); + withoutBypass.setBypassDnd(false); + mHelper.createNotificationChannel("com.example", UserHandle.getUid(0, 444), withoutBypass, + false, false, Process.SYSTEM_UID, true); + mHelper.createNotificationChannel("com.example", UserHandle.getUid(10, 444), withBypass, + false, false, Process.SYSTEM_UID, true); + + mHelper.syncChannelsBypassingDnd(); + + assertThat(mHelper.areChannelsBypassingDnd()).isFalse(); + } + + @Test public void testCreateDeletedChannel() throws Exception { long[] vibration = new long[]{100, 67, 145, 156}; NotificationChannel channel = @@ -3705,9 +3724,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); + loadByteArrayXml(preQXml.getBytes(), true, USER_SYSTEM); assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -3719,9 +3736,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setHideSilentStatusIcons(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -3789,9 +3803,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.canShowBadge(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3802,9 +3813,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3816,9 +3824,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.revokeNotificationDelegate(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3832,9 +3837,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O)); @@ -3888,9 +3890,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getAppLockedFields(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(BUBBLE_PREFERENCE_SELECTED, mHelper.getBubblePreference(PKG_O, UID_O)); @@ -3926,9 +3925,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getAppLockedFields(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE); @@ -4584,10 +4580,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testPlaceholderConversationId_shortcutRequired() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + "<channel id=\"id\" name=\"hi\" importance=\"3\" conv_id=\"foo:placeholder_id\"/>" @@ -4604,10 +4596,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testNormalConversationId_shortcutRequired() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + "<channel id=\"id\" name=\"hi\" importance=\"3\" conv_id=\"other\"/>" @@ -4624,10 +4612,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testNoConversationId_shortcutRequired() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + "<channel id=\"id\" name=\"hi\" importance=\"3\"/>" @@ -4644,10 +4628,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDeleted_noTime() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" + "<channel id=\"id\" name=\"hi\" importance=\"3\" deleted=\"true\"/>" @@ -4664,13 +4644,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDeleted_twice() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, UID_P, false); + assertTrue(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id", UID_P, false)); assertFalse(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id", @@ -4679,10 +4656,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDeleted_recentTime() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, UID_P, false); @@ -4698,9 +4671,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())), null); parser.nextTag(); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.readXml(parser, true, USER_SYSTEM); NotificationChannel nc = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true); @@ -4710,10 +4680,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testUnDelete_time() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false, UID_P, false); @@ -4732,10 +4698,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testDeleted_longTime() throws Exception { - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mPermissionManager, mLogger, - mAppOpsManager, mStatsEventBuilderFactory, false); - long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30); final String xml = "<ranking version=\"1\">\n" diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java index 6f6540627f67..05a1482b9be6 100644 --- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java @@ -112,7 +112,7 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase { // Show assistant. mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_ASSISTANT); sendKey(KEYCODE_POWER, true); - mPhoneWindowManager.assertAssistLaunch(); + mPhoneWindowManager.assertSearchManagerLaunchAssist(); // Show global actions. mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_GLOBAL_ACTIONS); diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java index c433e644a6a0..eab8757b7331 100644 --- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java @@ -16,11 +16,15 @@ package com.android.server.policy; +import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS; import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY; +import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; +import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY; +import android.content.ComponentName; import android.provider.Settings; import org.junit.Test; @@ -32,6 +36,9 @@ import org.junit.Test; * atest WmTests:StemKeyGestureTests */ public class StemKeyGestureTests extends ShortcutKeyTestBase { + + private static final String TEST_TARGET_ACTIVITY = "com.android.server.policy/.TestActivity"; + /** * Stem single key should not launch behavior during set up. */ @@ -63,6 +70,57 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { mPhoneWindowManager.assertOpenAllAppView(); } + /** + * Stem single key should not launch behavior during set up. + */ + @Test + public void stemSingleKey_launchTargetActivity() { + overrideBehavior( + STEM_PRIMARY_BUTTON_SHORT_PRESS, + SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideStartActivity(); + mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + mPhoneWindowManager.assumeResolveActivityNotNull(); + + ComponentName targetComponent = ComponentName.unflattenFromString(TEST_TARGET_ACTIVITY); + mPhoneWindowManager.overrideStemPressTargetActivity(targetComponent); + + sendKey(KEYCODE_STEM_PRIMARY); + + mPhoneWindowManager.assertActivityTargetLaunched(targetComponent); + } + + @Test + public void stemLongKey_triggerSearchServiceToLaunchAssist() { + overrideBehavior( + STEM_PRIMARY_BUTTON_LONG_PRESS, + LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.setupAssistForLaunch(); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + + sendKey(KEYCODE_STEM_PRIMARY, /* longPress= */ true); + mPhoneWindowManager.assertSearchManagerLaunchAssist(); + } + + @Test + public void stemLongKey_whenNoSearchService_triggerStatusBarToStartAssist() { + overrideBehavior( + STEM_PRIMARY_BUTTON_LONG_PRESS, + LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.setupAssistForLaunch(); + mPhoneWindowManager.overrideSearchManager(null); + mPhoneWindowManager.overrideStatusBarManagerInternal(); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + + sendKey(KEYCODE_STEM_PRIMARY, /* longPress= */ true); + mPhoneWindowManager.assertStatusBarStartAssist(); + } + + private void overrideBehavior(String key, int expectedBehavior) { Settings.Global.putLong(mContext.getContentResolver(), key, expectedBehavior); } diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index ef3a6edf9759..bc8f06a48ffb 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -59,9 +59,11 @@ import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.SearchManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.hardware.SensorPrivacyManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; @@ -230,6 +232,7 @@ class TestPhoneWindowManager { doReturn(mPackageManager).when(mContext).getPackageManager(); doReturn(mSensorPrivacyManager).when(mContext).getSystemService( eq(SensorPrivacyManager.class)); + doReturn(mSearchManager).when(mContext).getSystemService(eq(SearchManager.class)); doReturn(false).when(mPackageManager).hasSystemFeature(any()); try { doThrow(new PackageManager.NameNotFoundException("test")).when(mPackageManager) @@ -355,11 +358,7 @@ class TestPhoneWindowManager { case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST: break; case LONG_PRESS_POWER_ASSISTANT: - doNothing().when(mPhoneWindowManager).sendCloseSystemWindows(); - doReturn(true).when(mPhoneWindowManager).isUserSetupComplete(); - doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); - doReturn(mSearchManager).when(mContext) - .getSystemService(eq(Context.SEARCH_SERVICE)); + setupAssistForLaunch(); mPhoneWindowManager.mLongPressOnPowerAssistantTimeoutMs = 500; break; } @@ -433,6 +432,22 @@ class TestPhoneWindowManager { doReturn(isShowing).when(mKeyguardServiceDelegate).isShowing(); } + void setupAssistForLaunch() { + doNothing().when(mPhoneWindowManager).sendCloseSystemWindows(); + doReturn(true).when(mPhoneWindowManager).isUserSetupComplete(); + doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); + } + + void overrideSearchManager(SearchManager searchManager) { + mPhoneWindowManager.mSearchManager = searchManager; + } + + void assumeResolveActivityNotNull() { + ResolveInfo resolveInfo = new ResolveInfo(); + doReturn(resolveInfo).when(mPackageManager).resolveActivity(any(), anyInt()); + doReturn(mPackageManager).when(mContext).getPackageManager(); + } + void overrideKeyEventSource(int vendorId, int productId) { InputDevice device = new InputDevice.Builder().setId(1).setVendorId(vendorId).setProductId( productId).setSources(InputDevice.SOURCE_KEYBOARD).setKeyboardType( @@ -458,6 +473,10 @@ class TestPhoneWindowManager { doReturn(true).when(mPhoneWindowManager).isUserSetupComplete(); } + void overrideStemPressTargetActivity(ComponentName component) { + mPhoneWindowManager.mPrimaryShortPressTargetActivity = component; + } + /** * Below functions will check the policy behavior could be invoked. */ @@ -517,7 +536,7 @@ class TestPhoneWindowManager { .interceptPowerKeyDown(any(), anyBoolean(), any()); } - void assertAssistLaunch() { + void assertSearchManagerLaunchAssist() { waitForIdle(); verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any()); } @@ -540,6 +559,11 @@ class TestPhoneWindowManager { verify(mStatusBarManagerInternal).showRecentApps(anyBoolean()); } + void assertStatusBarStartAssist() { + waitForIdle(); + verify(mStatusBarManagerInternal).startAssist(any()); + } + void assertSwitchKeyboardLayout(int direction) { waitForIdle(); if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { @@ -613,6 +637,14 @@ class TestPhoneWindowManager { .startActivityAsUser(any(Intent.class), any(), any(UserHandle.class)); } + void assertActivityTargetLaunched(ComponentName targetActivity) { + waitForIdle(); + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS)) + .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class)); + Assert.assertEquals(targetActivity, intentCaptor.getValue().getComponent()); + } + void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent, int expectedKey, int expectedModifierState, String errorMsg) { waitForIdle(); diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index befc4a0120b1..4c978ad01a81 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -259,7 +259,24 @@ public class StorageStatsService extends IStorageStatsManager.Stub { // NOTE: No permissions required if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { - return FileUtils.roundStorageSize(mStorage.getPrimaryStorageSize()); + // As a safety measure, use the original implementation for the devices + // with storage size <= 512GB to prevent any potential regressions + final long roundedUserspaceBytes = mStorage.getPrimaryStorageSize(); + if (roundedUserspaceBytes <= DataUnit.GIGABYTES.toBytes(512)) { + return roundedUserspaceBytes; + } + + // Since 1TB devices can actually have either 1000GB or 1024GB, + // get the block device size and do just a small rounding if any at all + final long totalBytes = mStorage.getInternalStorageBlockDeviceSize(); + final long totalBytesRounded = FileUtils.roundStorageSize(totalBytes); + // If the storage size is 997GB-999GB, round it to a 1000GB to show + // 1TB in UI instead of 0.99TB. Same for 2TB, 4TB, 8TB etc. + if (totalBytesRounded - totalBytes <= DataUnit.GIGABYTES.toBytes(3)) { + return totalBytesRounded; + } else { + return totalBytes; + } } else { final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); if (vol == null) { @@ -286,15 +303,19 @@ public class StorageStatsService extends IStorageStatsManager.Stub { // Free space is usable bytes plus any cached data that we're // willing to automatically clear. To avoid user confusion, this // logic should be kept in sync with getAllocatableBytes(). + long freeBytes; if (isQuotaSupported(volumeUuid, PLATFORM_PACKAGE_NAME)) { final long cacheTotal = getCacheBytes(volumeUuid, PLATFORM_PACKAGE_NAME); final long cacheReserved = mStorage.getStorageCacheBytes(path, 0); final long cacheClearable = Math.max(0, cacheTotal - cacheReserved); - return path.getUsableSpace() + cacheClearable; + freeBytes = path.getUsableSpace() + cacheClearable; } else { - return path.getUsableSpace(); + freeBytes = path.getUsableSpace(); } + + Slog.d(TAG, "getFreeBytes: " + freeBytes); + return freeBytes; } finally { Binder.restoreCallingIdentity(token); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 4907134ffe1f..13e5ff15f1d8 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10408,7 +10408,7 @@ public class CarrierConfigManager { sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, new int[] {4 /* BUSY */}); sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false); - sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000); + sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 5000); sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]); sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP); @@ -10518,7 +10518,7 @@ public class CarrierConfigManager { auto_data_switch_rat_signal_score_string_bundle.putIntArray( "eHRPD", new int[]{10, 400, 600, 800, 1000}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( - "TD_SCDMA", new int[]{1, 100, 500, 1000}); + "TD_SCDMA", new int[]{1, 50, 100, 500, 1000}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "iDEN", new int[]{1, 2, 10, 50, 100}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( @@ -10536,7 +10536,7 @@ public class CarrierConfigManager { auto_data_switch_rat_signal_score_string_bundle.putIntArray( "EvDo_0", new int[]{300, 600, 1000, 1500, 2000}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( - "1xRTT", new int[]{50, 60, 70, 80}); + "1xRTT", new int[]{50, 60, 70, 80, 90}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "EDGE", new int[]{154, 169, 183, 192, 267}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index baacb574bde3..12ab5c32ad49 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3396,15 +3396,11 @@ public class SubscriptionManager { if (iSub != null) { groupUuid = iSub.createSubscriptionGroup(subIdArray, pkgForDebug); } else { - if (!isSystemProcess()) { - throw new IllegalStateException("telephony service is null."); - } + throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { loge("createSubscriptionGroup RemoteException " + ex); - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } + ex.rethrowAsRuntimeException(); } return groupUuid; @@ -3446,15 +3442,11 @@ public class SubscriptionManager { if (iSub != null) { iSub.addSubscriptionsIntoGroup(subIdArray, groupUuid, pkgForDebug); } else { - if (!isSystemProcess()) { - throw new IllegalStateException("telephony service is null."); - } + throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { loge("addSubscriptionsIntoGroup RemoteException " + ex); - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } + ex.rethrowAsRuntimeException(); } } @@ -3497,15 +3489,11 @@ public class SubscriptionManager { if (iSub != null) { iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, callingPackage); } else { - if (!isSystemProcess()) { - throw new IllegalStateException("telephony service is null."); - } + throw new IllegalStateException("telephony service is null."); } } catch (RemoteException ex) { loge("removeSubscriptionsFromGroup RemoteException " + ex); - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } + ex.rethrowAsRuntimeException(); } } @@ -3562,6 +3550,11 @@ public class SubscriptionManager { } } + // TODO(b/296125268) Really this method should throw, but it's common enough that for + // system callers it's worth having a little magic for the system process until it's + // made safer. + if (result == null) result = Collections.emptyList(); + return result; } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index c498216cf75b..9994002a4a37 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -57,7 +57,7 @@ import java.util.function.Consumer; */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE) @SystemApi -public class SatelliteManager { +public final class SatelliteManager { private static final String TAG = "SatelliteManager"; private static final ConcurrentHashMap<SatelliteDatagramCallback, ISatelliteDatagramCallback> @@ -1545,11 +1545,11 @@ public class SatelliteManager { */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - public void onDeviceAlignedWithSatellite(boolean isAligned) { + public void setDeviceAlignedWithSatellite(boolean isAligned) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - telephony.onDeviceAlignedWithSatellite(mSubId, isAligned); + telephony.setDeviceAlignedWithSatellite(mSubId, isAligned); } else { throw new IllegalStateException("telephony service is null."); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 0c3991d07ab6..06071feccf69 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2978,7 +2978,7 @@ interface ITelephony { */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - void onDeviceAlignedWithSatellite(int subId, in boolean isAligned); + void setDeviceAlignedWithSatellite(int subId, in boolean isAligned); /** * This API can be used by only CTS to update satellite vendor service package name. diff --git a/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java b/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java index 731be8e3d9f0..a77950fe4a2d 100644 --- a/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java +++ b/tests/PlatformCompatGating/src/com/android/compat/testing/DummyApi.java @@ -24,7 +24,7 @@ import android.os.ServiceManager; import com.android.internal.compat.IPlatformCompat; /** - * This is a dummy API to test gating + * This is a placeholder API to test gating * * @hide */ @@ -36,7 +36,7 @@ public class DummyApi { public static final long CHANGE_SYSTEM_SERVER = 666016; /** - * Dummy method + * Placeholder method * @return "A" if change is enabled, "B" otherwise. */ public static String dummyFunc() { @@ -47,7 +47,7 @@ public class DummyApi { } /** - * Dummy combined method + * Placeholder combined method * @return "0" if {@link CHANGE_ID_1} is disabled and {@link CHANGE_ID_2} is disabled, "1" if {@link CHANGE_ID_1} is disabled and {@link CHANGE_ID_2} is enabled, "2" if {@link CHANGE_ID_1} is enabled and {@link CHANGE_ID_2} is disabled, @@ -68,7 +68,7 @@ public class DummyApi { } /** - * Dummy api using system server API. + * Placeholder api using system server API. */ public static boolean dummySystemServer(Context context) { IPlatformCompat platformCompat = IPlatformCompat.Stub diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt index c4bc6001533e..43debb11013a 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt @@ -98,23 +98,23 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { (view.getParent() as ViewGroup).removeView(view) parent.addView(view) - view.findViewById<Button>(R.id.gainmap_metadata_done)!!.setOnClickListener { + view.requireViewById<Button>(R.id.gainmap_metadata_done).setOnClickListener { closeEditor() } - view.findViewById<Button>(R.id.gainmap_metadata_reset)!!.setOnClickListener { + view.requireViewById<Button>(R.id.gainmap_metadata_reset).setOnClickListener { resetGainmapMetadata() } updateMetadataUi() - val gainmapMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) - val gainmapMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) - val capacityMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) - val capacityMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) - val gammaSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gamma) - val offsetSdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) - val offsetHdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) + val gainmapMinSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) + val gainmapMaxSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) + val capacityMinSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) + val capacityMaxSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) + val gammaSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gamma) + val offsetSdrSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) + val offsetHdrSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) arrayOf(gainmapMinSeek, gainmapMaxSeek, capacityMinSeek, capacityMaxSeek, gammaSeek, offsetSdrSeek, offsetHdrSeek).forEach { it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{ @@ -139,13 +139,13 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { } private fun updateMetadataUi() { - val gainmapMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) - val gainmapMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) - val capacityMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) - val capacityMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) - val gammaSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gamma) - val offsetSdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) - val offsetHdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) + val gainmapMinSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) + val gainmapMaxSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) + val capacityMinSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) + val capacityMaxSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) + val gammaSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gamma) + val offsetSdrSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) + val offsetHdrSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) gainmapMinSeek.setProgress( ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt()) @@ -165,19 +165,19 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0) .toFloat() * maxProgress).toInt()) - parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val).setText( "%.3f".format(currentMetadata.ratioMin)) - parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val).setText( "%.3f".format(currentMetadata.ratioMax)) - parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymin_val).setText( "%.3f".format(currentMetadata.capacityMin)) - parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymax_val).setText( "%.3f".format(currentMetadata.capacityMax)) - parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gamma_val).setText( "%.3f".format(currentMetadata.gamma)) - parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val).setText( "%.5f".format(currentMetadata.offsetSdr)) - parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_offsethdr_val).setText( "%.5f".format(currentMetadata.offsetHdr)) } @@ -200,7 +200,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateGainmapMin(normalized: Float) { val newValue = minRatioMin + normalized * (maxRatioMin - minRatioMin) - parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val).setText( "%.3f".format(newValue)) currentMetadata.ratioMin = newValue gainmap.setRatioMin(newValue, newValue, newValue) @@ -209,7 +209,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateGainmapMax(normalized: Float) { val newValue = minRatioMax + normalized * (maxRatioMax - minRatioMax) - parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val).setText( "%.3f".format(newValue)) currentMetadata.ratioMax = newValue gainmap.setRatioMax(newValue, newValue, newValue) @@ -218,7 +218,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateCapacityMin(normalized: Float) { val newValue = minCapacityMin + normalized * (maxCapacityMin - minCapacityMin) - parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymin_val).setText( "%.3f".format(newValue)) currentMetadata.capacityMin = newValue gainmap.setMinDisplayRatioForHdrTransition(newValue) @@ -227,7 +227,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateCapacityMax(normalized: Float) { val newValue = minCapacityMax + normalized * (maxCapacityMax - minCapacityMax) - parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymax_val).setText( "%.3f".format(newValue)) currentMetadata.capacityMax = newValue gainmap.setDisplayRatioForFullHdr(newValue) @@ -236,7 +236,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { private fun updateGamma(normalized: Float) { val newValue = minGamma + normalized * (maxGamma - minGamma) - parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_gamma_val).setText( "%.3f".format(newValue)) currentMetadata.gamma = newValue gainmap.setGamma(newValue, newValue, newValue) @@ -248,7 +248,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { if (normalized > 0.0f ) { newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat() } - parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val).setText( "%.5f".format(newValue)) currentMetadata.offsetSdr = newValue gainmap.setEpsilonSdr(newValue, newValue, newValue) @@ -260,7 +260,7 @@ class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { if (normalized > 0.0f ) { newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat() } - parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText( + parent.requireViewById<TextView>(R.id.gainmap_metadata_offsethdr_val).setText( "%.5f".format(newValue)) currentMetadata.offsetHdr = newValue gainmap.setEpsilonHdr(newValue, newValue, newValue) diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt index 2f2578b87f35..41baeadf7a8c 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt @@ -190,9 +190,9 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont sensorManager?.unregisterListener(sensorListener) } - override fun onDraw(canvas: Canvas?) { + override fun onDraw(canvas: Canvas) { updateGlassRenderNode() - canvas?.drawRenderNode(renderNode) + canvas.drawRenderNode(renderNode) } fun resetGyroOffsets() { @@ -227,4 +227,4 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont renderNodeIsDirty = false } } -}
\ No newline at end of file +} diff --git a/tests/utils/testutils/java/com/android/internal/util/test/LocalServiceKeeperRule.java b/tests/utils/testutils/java/com/android/internal/util/test/LocalServiceKeeperRule.java new file mode 100644 index 000000000000..7012daf586d8 --- /dev/null +++ b/tests/utils/testutils/java/com/android/internal/util/test/LocalServiceKeeperRule.java @@ -0,0 +1,81 @@ +/* + * 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.util.test; + +import com.android.server.LocalServices; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * JUnit Rule helps override /restore {@link LocalServices} state. + */ +public class LocalServiceKeeperRule implements TestRule { + + private final Map<Class<?>, Object> mOverriddenServices = new HashMap<>(); + private final List<Class<?>> mAddedServices = new ArrayList<>(); + + private volatile boolean mRuleApplied = false; + + /** + * Overrides service in LocalServices. Service will be restored to original after test run. + */ + public <T> void overrideLocalService(Class<T> type, T service) { + if (!mRuleApplied) { + throw new IllegalStateException("Can't override service without applying rule"); + } + if (mOverriddenServices.containsKey(type) || mAddedServices.contains(type)) { + throw new IllegalArgumentException("Type already overridden: " + type); + } + + T currentService = LocalServices.getService(type); + if (currentService != null) { + mOverriddenServices.put(type, currentService); + LocalServices.removeServiceForTest(type); + } else { + mAddedServices.add(type); + } + LocalServices.addService(type, service); + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + public void evaluate() throws Throwable { + try { + mRuleApplied = true; + base.evaluate(); + } finally { + mRuleApplied = false; + mAddedServices.forEach(LocalServices::removeServiceForTest); + mOverriddenServices.forEach((clazz, service) -> { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService((Class) clazz, service); + }); + mAddedServices.clear(); + mOverriddenServices.clear(); + } + } + }; + } +} diff --git a/tests/utils/testutils/tests/src/com/android/internal/util/test/LocalServiceKeeperRuleTest.java b/tests/utils/testutils/tests/src/com/android/internal/util/test/LocalServiceKeeperRuleTest.java new file mode 100644 index 000000000000..12f2fae0a581 --- /dev/null +++ b/tests/utils/testutils/tests/src/com/android/internal/util/test/LocalServiceKeeperRuleTest.java @@ -0,0 +1,119 @@ +/* + * 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.util.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; + +import androidx.test.filters.SmallTest; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +@SmallTest +public class LocalServiceKeeperRuleTest { + + private final Description mDescription = Description.createSuiteDescription("Description"); + + LocalServiceKeeperRule mRule = new LocalServiceKeeperRule(); + + @After + public void tearDown() { + LocalServices.removeServiceForTest(TestService.class); + } + + @Test + public void testFailedIfCalledOutsideOfTheRule() { + TestService service = new TestService() {}; + + assertThrows(IllegalStateException.class, + () -> mRule.overrideLocalService(TestService.class, service)); + } + + @Test + public void testSetsLocalServiceIfNotPresent() throws Throwable { + LocalServices.removeServiceForTest(TestService.class); + TestService service = new TestService() {}; + + runInRuleApply(() -> { + mRule.overrideLocalService(TestService.class, service); + assertEquals(service, LocalServices.getService(TestService.class)); + }); + } + + @Test + public void testOverridesLocalServiceIfPresent() throws Throwable { + TestService service = new TestService() {}; + LocalServices.addService(TestService.class, service); + TestService overriddenService = new TestService() {}; + + runInRuleApply(() -> { + mRule.overrideLocalService(TestService.class, overriddenService); + assertEquals(overriddenService, LocalServices.getService(TestService.class)); + }); + } + + @Test + public void testDoesNotAllowToOverrideSameServiceTwice() throws Throwable { + TestService service = new TestService() {}; + + runInRuleApply(() -> { + mRule.overrideLocalService(TestService.class, service); + assertThrows(IllegalArgumentException.class, + () -> mRule.overrideLocalService(TestService.class, service)); + }); + } + + @Test + public void testRestroresLocalServiceAfterTestIfPresent() throws Throwable { + TestService expectedService = new TestService() {}; + LocalServices.addService(TestService.class, expectedService); + TestService overriddenService = new TestService() {}; + + runInRuleApply(() -> mRule.overrideLocalService(TestService.class, overriddenService)); + + assertEquals(expectedService, LocalServices.getService(TestService.class)); + } + + @Test + public void testRemovesLocalServiceAfterTestIfNotPresent() throws Throwable { + LocalServices.removeServiceForTest(TestService.class); + TestService service = new TestService() {}; + + runInRuleApply(() -> mRule.overrideLocalService(TestService.class, service)); + + assertNull(LocalServices.getService(TestService.class)); + } + + private void runInRuleApply(Runnable runnable) throws Throwable { + Statement testStatement = new Statement() { + @Override + public void evaluate() { + runnable.run(); + } + }; + mRule.apply(testStatement, mDescription).evaluate(); + } + + interface TestService { + } +} |