diff options
152 files changed, 3809 insertions, 690 deletions
diff --git a/Android.bp b/Android.bp index effd7ce2a63a..d9615991520a 100644 --- a/Android.bp +++ b/Android.bp @@ -580,7 +580,7 @@ metalava_framework_docs_args = "" + "--hide Todo " + "--hide Typo " + "--hide UnavailableSymbol " + - "--manifest $(location core/res/AndroidManifest.xml) " + "--manifest $(location :frameworks-base-core-AndroidManifest.xml) " packages_to_document = [ "android", @@ -617,7 +617,7 @@ stubs_defaults { sdk_version: "none", system_modules: "none", java_version: "1.8", - arg_files: ["core/res/AndroidManifest.xml"], + arg_files: [":frameworks-base-core-AndroidManifest.xml"], aidl: { local_include_dirs: [ "media/aidl", @@ -696,12 +696,3 @@ build = [ "ProtoLibraries.bp", "TestProtoLibraries.bp", ] - -java_api_contribution { - name: "api-stubs-docs-non-updatable-public-stubs", - api_surface: "public", - api_file: "core/api/current.txt", - visibility: [ - "//build/orchestrator/apis", - ], -} diff --git a/StubLibraries.bp b/StubLibraries.bp index 38413c22a3d5..b005591980c0 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -36,8 +36,8 @@ droidstubs { args: metalava_framework_docs_args, check_api: { current: { - api_file: "core/api/current.txt", - removed_api_file: "core/api/removed.txt", + api_file: ":non-updatable-current.txt", + removed_api_file: ":non-updatable-removed.txt", }, last_released: { api_file: ":android-non-updatable.api.public.latest", @@ -88,8 +88,8 @@ droidstubs { args: metalava_framework_docs_args + priv_apps, check_api: { current: { - api_file: "core/api/system-current.txt", - removed_api_file: "core/api/system-removed.txt", + api_file: ":non-updatable-system-current.txt", + removed_api_file: ":non-updatable-system-removed.txt", }, last_released: { api_file: ":android-non-updatable.api.system.latest", @@ -99,7 +99,7 @@ droidstubs { api_lint: { enabled: true, new_since: ":android.api.system.latest", - baseline_file: "core/api/system-lint-baseline.txt", + baseline_file: ":non-updatable-system-lint-baseline.txt", }, }, dists: [ @@ -127,12 +127,12 @@ droidstubs { args: metalava_framework_docs_args + test + priv_apps_in_stubs, check_api: { current: { - api_file: "core/api/test-current.txt", - removed_api_file: "core/api/test-removed.txt", + api_file: ":non-updatable-test-current.txt", + removed_api_file: ":non-updatable-test-removed.txt", }, api_lint: { enabled: true, - baseline_file: "core/api/test-lint-baseline.txt", + baseline_file: ":non-updatable-test-lint-baseline.txt", }, }, dists: [ @@ -172,8 +172,8 @@ droidstubs { args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs, check_api: { current: { - api_file: "core/api/module-lib-current.txt", - removed_api_file: "core/api/module-lib-removed.txt", + api_file: ":non-updatable-module-lib-current.txt", + removed_api_file: ":non-updatable-module-lib-removed.txt", }, last_released: { api_file: ":android-non-updatable.api.module-lib.latest", @@ -183,7 +183,7 @@ droidstubs { api_lint: { enabled: true, new_since: ":android.api.module-lib.latest", - baseline_file: "core/api/module-lib-lint-baseline.txt", + baseline_file: ":non-updatable-module-lib-lint-baseline.txt", }, }, dists: [ @@ -364,15 +364,15 @@ java_library { java_library { name: "android_system_server_stubs_current", - defaults: ["android_stubs_dists_default"], + defaults: [ + "android.jar_defaults", + "android_stubs_dists_default", + ], srcs: [":services-non-updatable-stubs"], installable: false, static_libs: [ "android_module_lib_stubs_current", ], - sdk_version: "none", - system_modules: "none", - java_version: "1.8", dist: { dir: "apistubs/android/system-server", }, @@ -575,20 +575,7 @@ droidstubs { droidstubs { name: "hwbinder-stubs-docs", - srcs: [ - "core/java/android/os/HidlSupport.java", - "core/java/android/os/HidlMemory.java", - "core/java/android/os/HwBinder.java", - "core/java/android/os/HwBlob.java", - "core/java/android/os/HwParcel.java", - "core/java/android/os/IHwBinder.java", - "core/java/android/os/IHwInterface.java", - "core/java/android/os/DeadObjectException.java", - "core/java/android/os/DeadSystemException.java", - "core/java/android/os/NativeHandle.java", - "core/java/android/os/RemoteException.java", - "core/java/android/util/AndroidException.java", - ], + srcs: [":hwbinder-stubs-srcs"], libs: ["framework-annotations-lib"], installable: false, sdk_version: "core_platform", @@ -610,12 +597,3 @@ java_library { ], visibility: ["//visibility:public"], } - -java_api_contribution { - name: "frameworks-base-core-api-module-lib-stubs", - api_surface: "module-lib", - api_file: "core/api/module-lib-current.txt", - visibility: [ - "//build/orchestrator/apis", - ], -} diff --git a/api/Android.bp b/api/Android.bp index 78ddc6ac2967..6e82f1c36eff 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -31,10 +31,12 @@ bootstrap_go_package { "blueprint", "soong", "soong-android", + "soong-bp2build", "soong-genrule", "soong-java", ], srcs: ["api.go"], + testSrcs: ["api_test.go"], pluginFor: ["soong_build"], } diff --git a/api/api.go b/api/api.go index 077ab9679ec9..25d97282035e 100644 --- a/api/api.go +++ b/api/api.go @@ -20,6 +20,7 @@ import ( "github.com/google/blueprint/proptools" "android/soong/android" + "android/soong/bazel" "android/soong/genrule" "android/soong/java" ) @@ -30,6 +31,7 @@ const i18n = "i18n.module.public.api" const virtualization = "framework-virtualization" var core_libraries_modules = []string{art, conscrypt, i18n} + // List of modules that are not yet updatable, and hence they can still compile // against hidden APIs. These modules are filtered out when building the // updatable-framework-module-impl (because updatable-framework-module-impl is @@ -59,6 +61,7 @@ type CombinedApisProperties struct { type CombinedApis struct { android.ModuleBase + android.BazelModuleBase properties CombinedApisProperties } @@ -99,6 +102,19 @@ type fgProps struct { Visibility []string } +type Bazel_module struct { + Bp2build_available *bool +} +type bazelProperties struct { + *Bazel_module +} + +var bp2buildNotAvailable = bazelProperties{ + &Bazel_module{ + Bp2build_available: proptools.BoolPtr(false), + }, +} + // Struct to pass parameters for the various merged [current|removed].txt file modules we create. type MergedTxtDefinition struct { // "current.txt" or "removed.txt" @@ -144,7 +160,7 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) { }, } props.Visibility = []string{"//visibility:public"} - ctx.CreateModule(genrule.GenRuleFactory, &props) + ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable) } func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) { @@ -174,7 +190,7 @@ func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, sys props := fgProps{} props.Name = proptools.StringPtr(i.name) props.Srcs = createSrcs(i.modules, i.tag) - ctx.CreateModule(android.FileGroupFactory, &props) + ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable) } } @@ -223,7 +239,7 @@ func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) { props.Tools = []string{"api_versions_trimmer"} props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)") props.Dists = []android.Dist{{Targets: []string{"sdk"}}} - ctx.CreateModule(genrule.GenRuleFactory, &props) + ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable) } } @@ -315,7 +331,7 @@ func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []str props.Name = proptools.StringPtr("all-modules-public-stubs-source") props.Srcs = createSrcs(modules, "{.public.stubs.source}") props.Visibility = []string{"//frameworks/base"} - ctx.CreateModule(android.FileGroupFactory, &props) + ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable) } func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) { @@ -389,9 +405,57 @@ func combinedApisModuleFactory() android.Module { module.AddProperties(&module.properties) android.InitAndroidModule(module) android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) }) + android.InitBazelModule(module) return module } +type bazelCombinedApisAttributes struct { + Scope bazel.StringAttribute + Base bazel.LabelAttribute + Deps bazel.LabelListAttribute +} + +// combined_apis bp2build converter +func (a *CombinedApis) ConvertWithBp2build(ctx android.TopDownMutatorContext) { + basePrefix := "non-updatable" + scopeNames := []string{"public", "system", "module-lib", "system-server"} + scopeToSuffix := map[string]string{ + "public": "-current.txt", + "system": "-system-current.txt", + "module-lib": "-module-lib-current.txt", + "system-server": "-system-server-current.txt", + } + + for _, scopeName := range scopeNames{ + suffix := scopeToSuffix[scopeName] + name := a.Name() + suffix + + var scope bazel.StringAttribute + scope.SetValue(scopeName) + + var base bazel.LabelAttribute + base.SetValue(android.BazelLabelForModuleDepSingle(ctx, basePrefix+suffix)) + + var deps bazel.LabelListAttribute + classpath := a.properties.Bootclasspath + if scopeName == "system-server" { + classpath = a.properties.System_server_classpath + } + deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, classpath)) + + attrs := bazelCombinedApisAttributes{ + Scope: scope, + Base: base, + Deps: deps, + } + props := bazel.BazelTargetModuleProperties{ + Rule_class: "merged_txts", + Bzl_load_location: "//build/bazel/rules/java:merged_txts.bzl", + } + ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, &attrs) + } +} + // Various utility methods below. // Creates an array of ":<m><tag>" for each m in <modules>. diff --git a/api/api_test.go b/api/api_test.go new file mode 100644 index 000000000000..15b695ca0d36 --- /dev/null +++ b/api/api_test.go @@ -0,0 +1,68 @@ +// 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 api + +import ( + "testing" + + "android/soong/android" + "android/soong/bp2build" +) + +func runCombinedApisTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2build.Bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) { + t.Helper() + (&tc).ModuleTypeUnderTest = "combined_apis" + (&tc).ModuleTypeUnderTestFactory = combinedApisModuleFactory + bp2build.RunBp2BuildTestCase(t, registrationCtxFunc, tc) +} + +func runCombinedApisTestCase(t *testing.T, tc bp2build.Bp2buildTestCase) { + t.Helper() + runCombinedApisTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {}) +} + +func TestCombinedApisGeneral(t *testing.T) { + runCombinedApisTestCase(t, bp2build.Bp2buildTestCase{ + Description: "combined_apis, general case", + Blueprint: `combined_apis { + name: "foo", + bootclasspath: ["bcp"], + system_server_classpath: ["ssc"], +} +`, + ExpectedBazelTargets: []string{ + bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-current.txt", bp2build.AttrNameToString{ + "scope": `"public"`, + "base": `":non-updatable-current.txt__BP2BUILD__MISSING__DEP"`, + "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`, + }), + bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-current.txt", bp2build.AttrNameToString{ + "scope": `"system"`, + "base": `":non-updatable-system-current.txt__BP2BUILD__MISSING__DEP"`, + "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`, + }), + bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-module-lib-current.txt", bp2build.AttrNameToString{ + "scope": `"module-lib"`, + "base": `":non-updatable-module-lib-current.txt__BP2BUILD__MISSING__DEP"`, + "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`, + }), + bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-server-current.txt", bp2build.AttrNameToString{ + "scope": `"system-server"`, + "base": `":non-updatable-system-server-current.txt__BP2BUILD__MISSING__DEP"`, + "deps": `[":ssc__BP2BUILD__MISSING__DEP"]`, + }), + }, + }) +} diff --git a/core/api/Android.bp b/core/api/Android.bp index 114a957674ae..71a2ca2903f6 100644 --- a/core/api/Android.bp +++ b/core/api/Android.bp @@ -13,7 +13,10 @@ // limitations under the License. package { - default_visibility: ["//visibility:private"], + default_visibility: [ + "//frameworks/base", + "//frameworks/base/api", + ], // 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" @@ -27,31 +30,33 @@ package { filegroup { name: "non-updatable-current.txt", srcs: ["current.txt"], - visibility: ["//frameworks/base/api"], } filegroup { name: "non-updatable-removed.txt", srcs: ["removed.txt"], - visibility: ["//frameworks/base/api"], } filegroup { name: "non-updatable-system-current.txt", srcs: ["system-current.txt"], - visibility: ["//frameworks/base/api"], } filegroup { name: "non-updatable-system-removed.txt", srcs: ["system-removed.txt"], - visibility: ["//frameworks/base/api"], +} + +filegroup { + name: "non-updatable-system-lint-baseline.txt", + srcs: ["system-lint-baseline.txt"], } filegroup { name: "non-updatable-module-lib-current.txt", srcs: ["module-lib-current.txt"], visibility: [ + "//frameworks/base", "//frameworks/base/api", "//cts/tests/signature/api", ], @@ -61,7 +66,46 @@ filegroup { name: "non-updatable-module-lib-removed.txt", srcs: ["module-lib-removed.txt"], visibility: [ + "//frameworks/base", "//frameworks/base/api", "//cts/tests/signature/api", ], } + +filegroup { + name: "non-updatable-module-lib-lint-baseline.txt", + srcs: ["module-lib-lint-baseline.txt"], +} + +filegroup { + name: "non-updatable-test-current.txt", + srcs: ["test-current.txt"], +} + +filegroup { + name: "non-updatable-test-removed.txt", + srcs: ["test-removed.txt"], +} + +filegroup { + name: "non-updatable-test-lint-baseline.txt", + srcs: ["test-lint-baseline.txt"], +} + +java_api_contribution { + name: "api-stubs-docs-non-updatable-public-stubs", + api_surface: "public", + api_file: "current.txt", + visibility: [ + "//build/orchestrator/apis", + ], +} + +java_api_contribution { + name: "frameworks-base-core-api-module-lib-stubs", + api_surface: "module-lib", + api_file: "module-lib-current.txt", + visibility: [ + "//build/orchestrator/apis", + ], +} diff --git a/core/api/current.txt b/core/api/current.txt index d41f8432f7d3..211f60350cfa 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -27308,6 +27308,7 @@ package android.media.tv { field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2 field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0 field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1 + field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id"; field public static final String TV_MESSAGE_TYPE_CLOSED_CAPTION = "CC"; field public static final String TV_MESSAGE_TYPE_WATERMARK = "Watermark"; field public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4; // 0x4 @@ -27436,6 +27437,7 @@ package android.media.tv { method public boolean onTrackballEvent(android.view.MotionEvent); method public abstract boolean onTune(android.net.Uri); method public boolean onTune(android.net.Uri, android.os.Bundle); + method public void onTvMessage(@NonNull String, @NonNull android.os.Bundle); method public void onUnblockContent(android.media.tv.TvContentRating); method public void setOverlayViewEnabled(boolean); } @@ -40643,6 +40645,7 @@ package android.service.credentials { public class CredentialEntry implements android.os.Parcelable { ctor public CredentialEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice); ctor public CredentialEntry(@NonNull android.service.credentials.BeginGetCredentialOption, @NonNull android.app.slice.Slice); + ctor public CredentialEntry(@NonNull String, @NonNull android.app.slice.Slice); method public int describeContents(); method @NonNull public String getBeginGetCredentialOptionId(); method @NonNull public android.app.slice.Slice getSlice(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a8a48b75b155..11a0f0b81ddd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2363,6 +2363,8 @@ package android.app.search { method @NonNull public String getTargetId(); method @NonNull public java.util.List<java.lang.String> getTargetIds(); method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int ACTION_DELETE = 9; // 0x9 + field public static final int ACTION_DISMISS = 10; // 0xa field public static final int ACTION_DRAGNDROP = 7; // 0x7 field public static final int ACTION_LAUNCH_KEYBOARD_FOCUS = 6; // 0x6 field public static final int ACTION_LAUNCH_TOUCH = 5; // 0x5 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 17c3d3399235..a3bc3922126b 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1956,6 +1956,10 @@ package android.media.tv { method public void removeHardwareDevice(int); } + public class TvView extends android.view.ViewGroup { + method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle); + } + } package android.media.tv.tuner { @@ -3973,6 +3977,18 @@ package android.window { method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction); } + public class WindowInfosListenerForTest { + ctor public WindowInfosListenerForTest(); + method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void addWindowInfosListener(@NonNull java.util.function.Consumer<java.util.List<android.window.WindowInfosListenerForTest.WindowInfo>>); + method public void removeWindowInfosListener(@NonNull java.util.function.Consumer<java.util.List<android.window.WindowInfosListenerForTest.WindowInfo>>); + } + + public static class WindowInfosListenerForTest.WindowInfo { + field @NonNull public final android.graphics.Rect bounds; + field @NonNull public final String name; + field @NonNull public final android.os.IBinder windowToken; + } + public class WindowOrganizer { ctor public WindowOrganizer(); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); diff --git a/core/java/Android.bp b/core/java/Android.bp index 738e2de7d44b..39695770ba58 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -285,6 +285,28 @@ filegroup { ], } +filegroup { + name: "hwbinder-stubs-srcs", + srcs: [ + "android/os/HidlSupport.java", + "android/os/HidlMemory.java", + "android/os/HwBinder.java", + "android/os/HwBlob.java", + "android/os/HwParcel.java", + "android/os/IHwBinder.java", + "android/os/IHwInterface.java", + "android/os/DeadObjectException.java", + "android/os/DeadSystemException.java", + "android/os/NativeHandle.java", + "android/os/RemoteException.java", + "android/util/AndroidException.java", + ], + visibility: [ + "//frameworks/base", + "//frameworks/base/api", + ], +} + cc_defaults { name: "incremental_default", cflags: [ diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 3c792a3ef5f9..b4068dbf0797 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4208,8 +4208,8 @@ public class ActivityManager { * processes to reclaim memory; the system will take care of restarting * these processes in the future as needed. * - * <p class="note">On devices with a {@link Build.VERSION#SECURITY_PATCH} of 2023-08-01 or - * greater, third party applications can only use this API to kill their own processes. + * <p class="note">On devices that run Android 14 or higher, + * third party applications can only use this API to kill their own processes. * </p> * * @param packageName The name of the package whose processes are to diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 5cad1aeb9fe7..ac928116481f 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -93,7 +93,7 @@ public class Instrumentation { private static final String TAG = "Instrumentation"; - private static final long CONNECT_TIMEOUT_MILLIS = 5000; + private static final long CONNECT_TIMEOUT_MILLIS = 60_000; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); diff --git a/core/java/android/app/search/SearchTargetEvent.java b/core/java/android/app/search/SearchTargetEvent.java index d4915afd5ce6..e8ef9227ad05 100644 --- a/core/java/android/app/search/SearchTargetEvent.java +++ b/core/java/android/app/search/SearchTargetEvent.java @@ -50,7 +50,9 @@ public final class SearchTargetEvent implements Parcelable { ACTION_LAUNCH_TOUCH, ACTION_LAUNCH_KEYBOARD_FOCUS, ACTION_DRAGNDROP, - ACTION_SURFACE_INVISIBLE + ACTION_SURFACE_INVISIBLE, + ACTION_DELETE, + ACTION_DISMISS }) @Retention(RetentionPolicy.SOURCE) public @interface ActionType {} @@ -114,6 +116,16 @@ public final class SearchTargetEvent implements Parcelable { */ public static final int ACTION_SURFACE_INVISIBLE = 8; + /** + * Constant that defines user deleted a target. + */ + public static final int ACTION_DELETE = 9; + + /** + * Constant that defines user dismissed a target. + */ + public static final int ACTION_DISMISS = 10; + private SearchTargetEvent(@NonNull List<String> targetIds, @Nullable String location, @ActionType int actionType, diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java index 85eac6edb6d2..e9cebd2e6af7 100644 --- a/core/java/android/service/credentials/CredentialEntry.java +++ b/core/java/android/service/credentials/CredentialEntry.java @@ -131,9 +131,7 @@ public class CredentialEntry implements Parcelable { * @param slice the slice containing the metadata to be shown on the UI. Must be * constructed through the androidx.credentials jetpack library. * - * @hide */ - // TODO: Unhide this constructor when the registry APIs are stable public CredentialEntry(@NonNull String type, @NonNull Slice slice) { mBeginGetCredentialOptionId = null; mType = requireNonNull(type, "type must not be null"); diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java index 94384b0c466d..f74f5332e8d3 100644 --- a/core/java/android/service/quicksettings/TileService.java +++ b/core/java/android/service/quicksettings/TileService.java @@ -533,7 +533,7 @@ public class TileService extends Service { * the calling package or if the calling user cannot act on behalf of the user from the * {@code context}.</li> * <li> {@link IllegalArgumentException} if the user of the {@code context} is not the - * current user.</li> + * current user. Only thrown for apps targeting {@link Build.VERSION_CODES#TIRAMISU}</li> * </ul> */ public static final void requestListeningState(Context context, ComponentName component) { diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index 406f446156a9..24a0355dd10e 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -244,6 +244,10 @@ public final class InputWindowHandle { window = iwindow; } + public @Nullable IBinder getWindowToken() { + return windowToken; + } + public IWindow getWindow() { if (window != null) { return window; diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index 85aea85907b5..4a7ed644f9e2 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -32,9 +32,10 @@ import java.util.Map; * * Use {@link #obtain} to retrieve a new instance of the class when you are going * to begin tracking. Put the motion events you receive into it with - * {@link #addMovement(MotionEvent)}. When you want to determine the velocity call - * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)} - * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id. + * {@link #addMovement(MotionEvent)}. When you want to determine the velocity, call + * {@link #computeCurrentVelocity(int)} and then call the velocity-getter methods like + * {@link #getXVelocity(int)}, {@link #getYVelocity(int)}, or {@link #getAxisVelocity(int, int)} + * to retrieve velocity for different axes and/or pointer IDs. */ public final class VelocityTracker { private static final SynchronizedPool<VelocityTracker> sPool = diff --git a/core/java/android/window/WindowInfosListener.java b/core/java/android/window/WindowInfosListener.java index 8db5a5eb0e47..42bb674d9d85 100644 --- a/core/java/android/window/WindowInfosListener.java +++ b/core/java/android/window/WindowInfosListener.java @@ -16,6 +16,8 @@ package android.window; +import android.Manifest; +import android.annotation.RequiresPermission; import android.graphics.Matrix; import android.util.Pair; import android.util.Size; @@ -49,10 +51,13 @@ public abstract class WindowInfosListener { /** * Register the WindowInfosListener. * + * Registering WindowInfosListeners should only be done within system_server and shell. + * * @return The cached values for InputWindowHandles and DisplayInfos. This is the last updated * value that was sent from SurfaceFlinger to this particular process. If there was nothing * registered previously, then the data can be empty. */ + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) public Pair<InputWindowHandle[], DisplayInfo[]> register() { return nativeRegister(mNativeListener); } @@ -65,8 +70,12 @@ public abstract class WindowInfosListener { } private static native long nativeCreate(WindowInfosListener thiz); + + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) private static native Pair<InputWindowHandle[], DisplayInfo[]> nativeRegister(long ptr); + private static native void nativeUnregister(long ptr); + private static native long nativeGetFinalizer(); /** diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java new file mode 100644 index 000000000000..429156fb980a --- /dev/null +++ b/core/java/android/window/WindowInfosListenerForTest.java @@ -0,0 +1,136 @@ +/* + * 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; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.TestApi; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.InputConfig; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pair; +import android.view.InputWindowHandle; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +/** + * Wrapper class to provide access to WindowInfosListener within tests. + * + * @hide + */ +@TestApi +public class WindowInfosListenerForTest { + + /** + * Window properties passed to {@code @WindowInfosListenerForTest#onWindowInfosChanged}. + */ + public static class WindowInfo { + /** + * The window's token. + */ + @NonNull + public final IBinder windowToken; + + /** + * The window's name. + */ + @NonNull + public final String name; + + /** + * The window's position and size in display space. + */ + @NonNull + public final Rect bounds; + + WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds) { + this.windowToken = windowToken; + this.name = name; + this.bounds = bounds; + } + } + + private static final String TAG = "WindowInfosListenerForTest"; + + private ArrayMap<Consumer<List<WindowInfo>>, WindowInfosListener> mListeners; + + public WindowInfosListenerForTest() { + mListeners = new ArrayMap<>(); + } + + /** + * Register a listener that is called when the system's list of visible windows has changes in + * position or visibility. + * + * @param consumer Consumer that is called with reverse Z ordered lists of WindowInfo instances + * where the first value is the topmost window. + */ + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) + public void addWindowInfosListener( + @NonNull Consumer<List<WindowInfo>> consumer) { + var calledWithInitialState = new CountDownLatch(1); + var listener = new WindowInfosListener() { + @Override + public void onWindowInfosChanged(InputWindowHandle[] windowHandles, + DisplayInfo[] displayInfos) { + try { + calledWithInitialState.await(); + } catch (InterruptedException exception) { + Log.e(TAG, + "Exception thrown while waiting for listener to be called with " + + "initial state"); + } + consumer.accept(buildWindowInfos(windowHandles)); + } + }; + mListeners.put(consumer, listener); + Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> initialState = + listener.register(); + consumer.accept(buildWindowInfos(initialState.first)); + calledWithInitialState.countDown(); + } + + /** + * Unregisters the listener. + */ + public void removeWindowInfosListener(@NonNull Consumer<List<WindowInfo>> consumer) { + WindowInfosListener listener = mListeners.remove(consumer); + if (listener == null) { + return; + } + listener.unregister(); + } + + private static List<WindowInfo> buildWindowInfos(InputWindowHandle[] windowHandles) { + var windowInfos = new ArrayList<WindowInfo>(windowHandles.length); + for (var handle : windowHandles) { + if ((handle.inputConfig & InputConfig.NOT_VISIBLE) != 0) { + continue; + } + var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight, + handle.frameBottom); + windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds)); + } + return windowInfos; + } +} diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index 4bc567abf27a..ad54004ec81c 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -20,20 +20,21 @@ #include <android_runtime/AndroidRuntime.h> #include <input/InputTransport.h> +#include <inttypes.h> #include <log/log.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedLocalRef.h> #include <utils/Looper.h> + +#include <optional> +#include <unordered_map> + #include "android_os_MessageQueue.h" #include "android_view_InputChannel.h" #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" #include "core_jni_helpers.h" -#include <inttypes.h> -#include <unordered_map> - - using android::base::Result; namespace android { @@ -67,7 +68,7 @@ private: jobject mSenderWeakGlobal; InputPublisher mInputPublisher; sp<MessageQueue> mMessageQueue; - std::unordered_map<uint32_t, uint32_t> mPublishedSeqMap; + std::unordered_map<uint32_t, std::optional<uint32_t>> mPublishedSeqMap; uint32_t mNextPublishedSeq; @@ -165,8 +166,14 @@ status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent getInputChannelName().c_str(), status); return status; } + // mPublishedSeqMap tracks all sequences published from this sender. Only the last + // sequence number is used to signal this motion event is finished. + if (i == event->getHistorySize()) { + mPublishedSeqMap.emplace(publishedSeq, seq); + } else { + mPublishedSeqMap.emplace(publishedSeq, std::nullopt); + } } - mPublishedSeqMap.emplace(publishedSeq, seq); return OK; } @@ -277,8 +284,16 @@ bool NativeInputEventSender::notifyConsumerResponse( // does something wrong and sends bad data. Just ignore and process other events. return true; } - const uint32_t seq = it->second; + + const std::optional<uint32_t> seqOptional = it->second; mPublishedSeqMap.erase(it); + // If this optional does not have a value, it means we are processing an event that had history + // and was split. There are more events coming, so we can't call 'dispatchInputEventFinished' + // yet. The final split event will have a valid sequence number. + if (!seqOptional.has_value()) { + return true; + } + const uint32_t seq = seqOptional.value(); if (kDebugDispatchCycle) { ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.", diff --git a/core/res/Android.bp b/core/res/Android.bp index 3c4bac8156d2..b71995f899c5 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -178,3 +178,12 @@ genrule { " > $(out)", tools: ["xmllint"], } + +filegroup { + name: "frameworks-base-core-AndroidManifest.xml", + srcs: ["AndroidManifest.xml"], + visibility: [ + "//frameworks/base", + "//frameworks/base/api", + ], +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index 480bf93b2ddb..53bf42a3c911 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -39,6 +39,9 @@ import android.window.TaskSnapshot; * Represents the content overlay used during the entering PiP animation. */ public abstract class PipContentOverlay { + // Fixed string used in WMShellFlickerTests + protected static final String LAYER_NAME = "PipContentOverlay"; + protected SurfaceControl mLeash; /** Attaches the internal {@link #mLeash} to the given parent leash. */ @@ -86,7 +89,7 @@ public abstract class PipContentOverlay { mContext = context; mLeash = new SurfaceControl.Builder(new SurfaceSession()) .setCallsite(TAG) - .setName(TAG) + .setName(LAYER_NAME) .setColorLayer() .build(); } @@ -139,7 +142,7 @@ public abstract class PipContentOverlay { mSourceRectHint = new Rect(sourceRectHint); mLeash = new SurfaceControl.Builder(new SurfaceSession()) .setCallsite(TAG) - .setName(TAG) + .setName(LAYER_NAME) .build(); } @@ -194,7 +197,7 @@ public abstract class PipContentOverlay { prepareAppIconOverlay(activityInfo); mLeash = new SurfaceControl.Builder(new SurfaceSession()) .setCallsite(TAG) - .setName(TAG) + .setName(LAYER_NAME) .build(); } 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 f11836ea5bee..e9d257139779 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 @@ -1594,7 +1594,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // source rect hint to enter PiP use bounds animation. if (sourceHintRect == null) { if (SystemProperties.getBoolean( - "persist.wm.debug.enable_pip_app_icon_overlay", false)) { + "persist.wm.debug.enable_pip_app_icon_overlay", true)) { animator.setAppIconContentOverlay( mContext, currentBounds, mTaskInfo.topActivityInfo); } else { 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 6b0337d3fb4a..a91a3424f3a0 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 @@ -804,7 +804,7 @@ public class PipTransition extends PipTransitionController { // We use content overlay when there is no source rect hint to enter PiP use bounds // animation. if (SystemProperties.getBoolean( - "persist.wm.debug.enable_pip_app_icon_overlay", false)) { + "persist.wm.debug.enable_pip_app_icon_overlay", true)) { animator.setAppIconContentOverlay( mContext, currentBounds, taskInfo.topActivityInfo); } else { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 70d3b3509b2f..146abea2bc31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -764,17 +764,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (pendingIntent2 == null) { - // Launching a solo task. - ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); - activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); - options1 = activityOptions.toBundle(); - addActivityOptions(options1, null /* launchTarget */); - if (shortcutInfo1 != null) { - wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); - } else { - wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); - } - mSyncQueue.queue(wct); + // Launching a solo intent or shortcut as fullscreen. + launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1, + options1, adapter, wct); return; } @@ -797,13 +789,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (taskId == INVALID_TASK_ID) { - // Launching a solo task. - ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); - activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); - options1 = activityOptions.toBundle(); - addActivityOptions(options1, null /* launchTarget */); - wct.sendPendingIntent(pendingIntent, fillInIntent, options1); - mSyncQueue.queue(wct); + // Launching a solo intent as fullscreen. + launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1, + adapter, wct); return; } @@ -822,13 +810,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); if (options1 == null) options1 = new Bundle(); if (taskId == INVALID_TASK_ID) { - // Launching a solo task. - ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); - activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); - options1 = activityOptions.toBundle(); - addActivityOptions(options1, null /* launchTarget */); - wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); - mSyncQueue.queue(wct); + // Launching a solo shortcut as fullscreen. + launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct); return; } @@ -838,6 +821,49 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, instanceId); } + private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent, + @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo, + @Nullable Bundle options, RemoteAnimationAdapter adapter, + WindowContainerTransaction wct) { + LegacyTransitions.ILegacyTransition transition = + (transit, apps, wallpapers, nonApps, finishedCallback, t) -> { + if (apps == null || apps.length == 0) { + onRemoteAnimationFinished(apps); + t.apply(); + try { + adapter.getRunner().onAnimationCancelled(mKeyguardShowing); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + return; + } + + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING) { + t.show(apps[i].leash); + } + } + t.apply(); + + try { + adapter.getRunner().onAnimationStart( + transit, apps, wallpapers, nonApps, finishedCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + }; + + addActivityOptions(options, null /* launchTarget */); + if (shortcutInfo != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo, options); + } else if (pendingIntent != null) { + wct.sendPendingIntent(pendingIntent, fillInIntent, options); + } else { + Slog.e(TAG, "Pending intent and shortcut are null is invalid case."); + } + mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); + } + private void startWithLegacyTransition(WindowContainerTransaction wct, @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent, @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions, @@ -894,23 +920,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (options == null) options = new Bundle(); addActivityOptions(options, mMainStage); - options = wrapAsSplitRemoteAnimation(adapter, options); updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + wct.setForceTranslucent(mRootTaskInfo.token, false); // TODO(b/268008375): Merge APIs to start a split pair into one. if (mainTaskId != INVALID_TASK_ID) { + options = wrapAsSplitRemoteAnimation(adapter, options); wct.startTask(mainTaskId, options); - } else if (mainShortcutInfo != null) { - wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options); + mSyncQueue.queue(wct); } else { - wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options); + if (mainShortcutInfo != null) { + wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options); + } else { + wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options); + } + mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct); } - wct.reorder(mRootTaskInfo.token, true); - wct.setForceTranslucent(mRootTaskInfo.token, false); - - mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { setDividerVisibility(true, t); }); @@ -967,6 +995,54 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return activityOptions.toBundle(); } + private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation( + RemoteAnimationAdapter adapter) { + LegacyTransitions.ILegacyTransition transition = + (transit, apps, wallpapers, nonApps, finishedCallback, t) -> { + if (apps == null || apps.length == 0) { + onRemoteAnimationFinished(apps); + t.apply(); + try { + adapter.getRunner().onAnimationCancelled(mKeyguardShowing); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + return; + } + + // Wrap the divider bar into non-apps target to animate together. + nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps, + getDividerBarLegacyTarget()); + + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING) { + t.show(apps[i].leash); + // Reset the surface position of the opening app to prevent offset. + t.setPosition(apps[i].leash, 0, 0); + } + } + t.apply(); + + IRemoteAnimationFinishedCallback wrapCallback = + new IRemoteAnimationFinishedCallback.Stub() { + @Override + public void onAnimationFinished() throws RemoteException { + onRemoteAnimationFinished(apps); + finishedCallback.onAnimationFinished(); + } + }; + Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication()); + try { + adapter.getRunner().onAnimationStart( + transit, apps, wallpapers, nonApps, wrapCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + }; + + return transition; + } + private void setEnterInstanceId(InstanceId instanceId) { if (instanceId != null) { mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER); @@ -993,6 +1069,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) { + mIsDividerRemoteAnimating = false; + mShouldUpdateRecents = true; + mSplitRequest = null; + // If any stage has no child after finished animation, that side of the split will display + // nothing. This might happen if starting the same app on the both sides while not + // supporting multi-instance. Exit the split screen and expand that app to full screen. + if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { + mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 + ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + mSplitUnsupportedToast.show(); + return; + } + + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct); + prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct); + mSyncQueue.queue(evictWct); + } + + /** * Collects all the current child tasks of a specific split and prepares transaction to evict * them to display. diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java index 5ecec4ddd1ad..3125f088c72b 100644 --- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java +++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java @@ -72,6 +72,7 @@ public final class LowLightDreamManager { public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2; private final DreamManager mDreamManager; + private final LowLightTransitionCoordinator mLowLightTransitionCoordinator; @Nullable private final ComponentName mLowLightDreamComponent; @@ -81,8 +82,10 @@ public final class LowLightDreamManager { @Inject public LowLightDreamManager( DreamManager dreamManager, + LowLightTransitionCoordinator lowLightTransitionCoordinator, @Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) { mDreamManager = dreamManager; + mLowLightTransitionCoordinator = lowLightTransitionCoordinator; mLowLightDreamComponent = lowLightDreamComponent; } @@ -111,7 +114,9 @@ public final class LowLightDreamManager { mAmbientLightMode = ambientLightMode; - mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT - ? mLowLightDreamComponent : null); + boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT; + mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight, + () -> mDreamManager.setSystemDreamComponent( + shouldEnterLowLight ? mLowLightDreamComponent : null)); } } diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java new file mode 100644 index 000000000000..874a2d5af75e --- /dev/null +++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java @@ -0,0 +1,111 @@ +/* + * 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.dream.lowlight; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.Nullable; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Helper class that allows listening and running animations before entering or exiting low light. + */ +@Singleton +public class LowLightTransitionCoordinator { + /** + * Listener that is notified before low light entry. + */ + public interface LowLightEnterListener { + /** + * Callback that is notified before the device enters low light. + * + * @return an optional animator that will be waited upon before entering low light. + */ + Animator onBeforeEnterLowLight(); + } + + /** + * Listener that is notified before low light exit. + */ + public interface LowLightExitListener { + /** + * Callback that is notified before the device exits low light. + * + * @return an optional animator that will be waited upon before exiting low light. + */ + Animator onBeforeExitLowLight(); + } + + private LowLightEnterListener mLowLightEnterListener; + private LowLightExitListener mLowLightExitListener; + + @Inject + public LowLightTransitionCoordinator() { + } + + /** + * Sets the listener for the low light enter event. + * + * Only one listener can be set at a time. This method will overwrite any previously set + * listener. Null can be used to unset the listener. + */ + public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) { + mLowLightEnterListener = lowLightEnterListener; + } + + /** + * Sets the listener for the low light exit event. + * + * Only one listener can be set at a time. This method will overwrite any previously set + * listener. Null can be used to unset the listener. + */ + public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) { + mLowLightExitListener = lowLightExitListener; + } + + /** + * Notifies listeners that the device is about to enter or exit low light. + * + * @param entering true if listeners should be notified before entering low light, false if this + * is notifying before exiting. + * @param callback callback that will be run after listeners complete. + */ + void notifyBeforeLowLightTransition(boolean entering, Runnable callback) { + Animator animator = null; + + if (entering && mLowLightEnterListener != null) { + animator = mLowLightEnterListener.onBeforeEnterLowLight(); + } else if (!entering && mLowLightExitListener != null) { + animator = mLowLightExitListener.onBeforeExitLowLight(); + } + + // If the listener returned an animator to indicate it was running an animation, run the + // callback after the animation completes, otherwise call the callback directly. + if (animator != null) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + callback.run(); + } + }); + } else { + callback.run(); + } + } +} diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java index 91a170f7ae14..4b95d8c84bac 100644 --- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java +++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java @@ -21,7 +21,10 @@ import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -44,44 +47,52 @@ public class LowLightDreamManagerTest { private DreamManager mDreamManager; @Mock + private LowLightTransitionCoordinator mTransitionCoordinator; + + @Mock private ComponentName mDreamComponent; + LowLightDreamManager mLowLightDreamManager; + @Before public void setUp() { MockitoAnnotations.initMocks(this); + + // Automatically run any provided Runnable to mTransitionCoordinator to simplify testing. + doAnswer(invocation -> { + ((Runnable) invocation.getArgument(1)).run(); + return null; + }).when(mTransitionCoordinator).notifyBeforeLowLightTransition(anyBoolean(), + any(Runnable.class)); + + mLowLightDreamManager = new LowLightDreamManager(mDreamManager, mTransitionCoordinator, + mDreamComponent); } @Test public void setAmbientLightMode_lowLight_setSystemDream() { - final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, - mDreamComponent); - - lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); + mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); + verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any()); verify(mDreamManager).setSystemDreamComponent(mDreamComponent); } @Test public void setAmbientLightMode_regularLight_clearSystemDream() { - final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, - mDreamComponent); - - lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR); + mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR); + verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any()); verify(mDreamManager).setSystemDreamComponent(null); } @Test public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() { - final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, - mDreamComponent); - // Set to low light first. - lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); + mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); clearInvocations(mDreamManager); // Return to default unknown mode. - lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN); + mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN); verify(mDreamManager).setSystemDreamComponent(null); } @@ -89,7 +100,7 @@ public class LowLightDreamManagerTest { @Test public void setAmbientLightMode_dreamComponentNotSet_doNothing() { final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager, - null /*dream component*/); + mTransitionCoordinator, null /*dream component*/); lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT); diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java new file mode 100644 index 000000000000..81e1e33d6220 --- /dev/null +++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java @@ -0,0 +1,113 @@ +/* + * 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.dream.lowlight; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.animation.Animator; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class LowLightTransitionCoordinatorTest { + @Mock + private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener; + + @Mock + private LowLightTransitionCoordinator.LowLightExitListener mExitListener; + + @Mock + private Animator mAnimator; + + @Captor + private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor; + + @Mock + private Runnable mRunnable; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void onEnterCalledOnListeners() { + LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator(); + + coordinator.setLowLightEnterListener(mEnterListener); + + coordinator.notifyBeforeLowLightTransition(true, mRunnable); + + verify(mEnterListener).onBeforeEnterLowLight(); + verify(mRunnable).run(); + } + + @Test + public void onExitCalledOnListeners() { + LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator(); + + coordinator.setLowLightExitListener(mExitListener); + + coordinator.notifyBeforeLowLightTransition(false, mRunnable); + + verify(mExitListener).onBeforeExitLowLight(); + verify(mRunnable).run(); + } + + @Test + public void listenerNotCalledAfterRemoval() { + LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator(); + + coordinator.setLowLightEnterListener(mEnterListener); + coordinator.setLowLightEnterListener(null); + + coordinator.notifyBeforeLowLightTransition(true, mRunnable); + + verifyZeroInteractions(mEnterListener); + verify(mRunnable).run(); + } + + @Test + public void runnableCalledAfterAnimationEnds() { + when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator); + + LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator(); + coordinator.setLowLightEnterListener(mEnterListener); + + coordinator.notifyBeforeLowLightTransition(true, mRunnable); + + // Animator listener is added and the runnable is not run yet. + verify(mAnimator).addListener(mAnimatorListenerCaptor.capture()); + verifyZeroInteractions(mRunnable); + + // Runnable is run once the animation ends. + mAnimatorListenerCaptor.getValue().onAnimationEnd(null); + verify(mRunnable).run(); + } +} diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index f17129c8d953..1af60b2f5fae 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -108,8 +108,9 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo std::move(data), std::string_view(fontPath.c_str(), fontPath.size()), fontPtr, fontSize, ttcIndex, builder->axes); if (minikinFont == nullptr) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "Failed to create internal object. maybe invalid font data."); + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Failed to create internal object. maybe invalid font data. filePath %s", + fontPath.c_str()); return 0; } uint32_t localeListId = minikin::registerLocaleList(langTagStr.c_str()); diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index f85bdee18967..5f5e214357ea 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -252,10 +252,10 @@ public class AudioMix { if (o == null || getClass() != o.getClass()) return false; final AudioMix that = (AudioMix) o; - return (this.mRouteFlags == that.mRouteFlags) - && (this.mRule == that.mRule) - && (this.mMixType == that.mMixType) - && (this.mFormat == that.mFormat); + return (mRouteFlags == that.mRouteFlags) + && (mMixType == that.mMixType) + && Objects.equals(mRule, that.mRule) + && Objects.equals(mFormat, that.mFormat); } /** @hide */ diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index 440447e5ec1d..ce9773312a10 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -24,6 +24,7 @@ import android.os.Parcelable; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Objects; @@ -50,7 +51,8 @@ public class AudioPolicyConfig implements Parcelable { mMixes = conf.mMixes; } - AudioPolicyConfig(ArrayList<AudioMix> mixes) { + @VisibleForTesting + public AudioPolicyConfig(ArrayList<AudioMix> mixes) { mMixes = mixes; } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index e9aa321018f3..113c85802fa2 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -118,6 +118,9 @@ interface ITvInputManager { void requestAd(in IBinder sessionToken, in AdRequest request, int userId); void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId); + // For TV Message + void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId); + // For TV input hardware binding List<TvInputHardwareInfo> getHardwareList(); ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback, diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 82875e5d72f1..165a9ddc26e9 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -76,4 +76,7 @@ oneway interface ITvInputSession { // For ad request void requestAd(in AdRequest request); void notifyAdBuffer(in AdBuffer buffer); + + // For TV messages + void notifyTvMessage(in String type, in Bundle data); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 465b617db1f4..8389706b501c 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -78,6 +78,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_SELECT_AUDIO_PRESENTATION = 29; private static final int DO_TIME_SHIFT_SET_MODE = 30; private static final int DO_SET_TV_MESSAGE_ENABLED = 31; + private static final int DO_NOTIFY_TV_MESSAGE = 32; private final boolean mIsRecordingSession; private final HandlerCaller mCaller; @@ -277,6 +278,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj); break; } + case DO_NOTIFY_TV_MESSAGE: { + SomeArgs args = (SomeArgs) msg.obj; + mTvInputSessionImpl.onTvMessageReceived((String) args.arg1, (Bundle) args.arg2); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -463,6 +469,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer)); } + @Override + public void notifyTvMessage(String type, Bundle data) { + mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data)); + } + private final class TvInputEventReceiver extends InputEventReceiver { TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 8459538f102d..55a753f663c8 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -144,6 +144,13 @@ public final class TvInputManager { @StringDef({TV_MESSAGE_TYPE_WATERMARK, TV_MESSAGE_TYPE_CLOSED_CAPTION}) public @interface TvMessageType {} + /** + * This constant is used as a {@link Bundle} key for TV messages. The value of the key + * identifies the stream on the TV input source for which the watermark event is relevant to. + */ + public static final String TV_MESSAGE_KEY_STREAM_ID = + "android.media.tv.TvInputManager.stream_id"; + static final int VIDEO_UNAVAILABLE_REASON_START = 0; static final int VIDEO_UNAVAILABLE_REASON_END = 18; @@ -3221,6 +3228,17 @@ public final class TvInputManager { } /** + * Sends TV messages to the service for testing purposes + */ + public void notifyTvMessage(@NonNull @TvMessageType String type, @NonNull Bundle data) { + try { + mService.notifyTvMessage(mToken, type, data, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Starts TV program recording in the current recording session. * * @param programUri The URI for the TV program to record as a hint, built by diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 4bc137da6f00..9f40d704f7ac 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1491,7 +1491,18 @@ public abstract class TvInputService extends Service { * {@code false} otherwise. */ public void onSetTvMessageEnabled(@NonNull @TvInputManager.TvMessageType String type, - boolean enabled){ + boolean enabled) { + } + + /** + * Called when a TV message is received + * + * @param type The type of message received, such as + * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} + * @param data The raw data of the message + */ + public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type, + @NonNull Bundle data) { } /** @@ -2043,6 +2054,10 @@ public abstract class TvInputService extends Service { onAdBuffer(buffer); } + void onTvMessageReceived(String type, Bundle data) { + onTvMessage(type, data); + } + /** * Takes care of dispatching incoming input events and tells whether the event was handled. */ diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 3ef61f289e23..5aeed1fbbaf5 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.AttributionSource; import android.content.Context; import android.content.pm.PackageManager; @@ -640,6 +641,20 @@ public class TvView extends ViewGroup { } } + + /** + * Sends TV messages to the session for testing purposes + * + * @hide + */ + @TestApi + public void notifyTvMessage(@TvInputManager.TvMessageType @NonNull String type, + @NonNull Bundle data) { + if (mSession != null) { + mSession.notifyTvMessage(type, data); + } + } + /** * Sets the callback to be invoked when the time shift position is changed. * diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp index 63292ce766f8..4624dfe70756 100644 --- a/media/tests/AudioPolicyTest/Android.bp +++ b/media/tests/AudioPolicyTest/Android.bp @@ -14,6 +14,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.rules", "guava", + "guava-android-testlib", "hamcrest-library", "platform-test-annotations", ], diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java new file mode 100644 index 000000000000..bbca8823dde4 --- /dev/null +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java @@ -0,0 +1,158 @@ +/* + * 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.audiopolicytest; + +import static android.media.AudioFormat.CHANNEL_OUT_MONO; +import static android.media.AudioFormat.CHANNEL_OUT_STEREO; +import static android.media.AudioFormat.ENCODING_PCM_16BIT; +import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR; +import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS; +import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID; +import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID; + +import android.media.AudioFormat; +import android.media.audiopolicy.AudioMix; +import android.media.audiopolicy.AudioMixingRule; +import android.media.audiopolicy.AudioPolicyConfig; +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.common.testing.EqualsTester; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests for AudioMix. + * + * Run with "atest AudioMixUnitTests". + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +public class AudioMixUnitTests { + private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM = + new AudioFormat.Builder() + .setSampleRate(44000) + .setChannelMask(CHANNEL_OUT_STEREO) + .setEncoding(ENCODING_PCM_16BIT).build(); + private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM = + new AudioFormat.Builder() + .setSampleRate(16000) + .setChannelMask(CHANNEL_OUT_MONO) + .setEncoding(ENCODING_PCM_16BIT).build(); + private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM = + new AudioFormat.Builder() + .setSampleRate(16000) + .setChannelMask(AudioFormat.CHANNEL_IN_MONO) + .setEncoding(ENCODING_PCM_16BIT).build(); + + @Test + public void testEquals() { + final EqualsTester equalsTester = new EqualsTester(); + + // --- Equality group 1 + final AudioMix playbackAudioMixWithSessionId42AndUid123 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + final AudioMix playbackAudioMixWithUid123AndSessionId42 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_UID, 123) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42).build()) + .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + equalsTester.addEqualityGroup( + playbackAudioMixWithSessionId42AndUid123, + playbackAudioMixWithUid123AndSessionId42, + writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123), + writeToAndFromParcel(playbackAudioMixWithUid123AndSessionId42)); + + // --- Equality group 2 + final AudioMix recordingAudioMixWithSessionId42AndUid123 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_INJECTOR) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + final AudioMix recordingAudioMixWithUid123AndSessionId42 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_INJECTOR) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123, + recordingAudioMixWithUid123AndSessionId42, + writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123), + writeToAndFromParcel(recordingAudioMixWithUid123AndSessionId42)); + + // --- Equality group 3 + final AudioMix recordingAudioMixWithSessionId42AndUid123Render = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_INJECTOR) + .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags( + AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build(); + equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render, + writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render)); + + // --- Equality group 4 + final AudioMix playbackAudioMixWithUid123 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_UID, 123).build()) + .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + equalsTester.addEqualityGroup(playbackAudioMixWithUid123, + writeToAndFromParcel(playbackAudioMixWithUid123)); + + // --- Equality group 5 + final AudioMix playbackAudioMixWithUid42 = + new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_UID, 42).build()) + .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + equalsTester.addEqualityGroup(playbackAudioMixWithUid42, + writeToAndFromParcel(playbackAudioMixWithUid42)); + + equalsTester.testEquals(); + } + + private static AudioMix writeToAndFromParcel(AudioMix audioMix) { + AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix))); + Parcel parcel = Parcel.obtain(); + apc.writeToParcel(parcel, /*flags=*/0); + parcel.setDataPosition(0); + AudioMix unmarshalledMix = + AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0); + parcel.recycle(); + return unmarshalledMix; + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index ce18335769a8..e7de8b3f128c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -22,6 +22,7 @@ import android.util.Log import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.result.ActivityResult import androidx.activity.result.IntentSenderRequest +import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -36,6 +37,8 @@ import com.android.credentialmanager.createflow.CreateCredentialUiState import com.android.credentialmanager.createflow.CreateScreenState import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.GetScreenState +import com.android.credentialmanager.logging.UIMetrics +import com.android.internal.logging.UiEventLogger.UiEventEnum /** One and only one of create or get state can be active at any given time. */ data class UiState( @@ -56,6 +59,8 @@ class CredentialSelectorViewModel( var uiState by mutableStateOf(credManRepo.initState()) private set + var uiMetrics: UIMetrics = UIMetrics() + /**************************************************************************/ /***** Shared Callbacks *****/ /**************************************************************************/ @@ -76,6 +81,10 @@ class CredentialSelectorViewModel( fun onNewCredentialManagerRepo(credManRepo: CredentialManagerRepo) { this.credManRepo = credManRepo uiState = credManRepo.initState() + + if (this.credManRepo.requestInfo.token != credManRepo.requestInfo.token) { + this.uiMetrics.resetInstanceId() + } } fun launchProviderUi( @@ -374,4 +383,9 @@ class CredentialSelectorViewModel( onInternalError() } } + + @Composable + fun logUiEvent(uiEventEnum: UiEventEnum) { + this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo.appPackageName) + } }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 9fe789934f1b..524077715b10 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -67,6 +67,8 @@ import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.PasskeyBenefitRow import com.android.credentialmanager.common.ui.HeadlineText import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor +import com.android.credentialmanager.logging.CreateCredentialEvent +import com.android.internal.logging.UiEventLogger.UiEventEnum @Composable fun CreateCredentialScreen( @@ -85,81 +87,93 @@ fun CreateCredentialScreen( ProviderActivityState.NOT_APPLICABLE -> { when (createCredentialUiState.currentScreenState) { CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard( - onConfirm = viewModel::createFlowOnConfirmIntro, - onLearnMore = viewModel::createFlowOnLearnMore, + onConfirm = viewModel::createFlowOnConfirmIntro, + onLearnMore = viewModel::createFlowOnLearnMore, + onLog = { viewModel.logUiEvent(it) }, ) CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard( - requestDisplayInfo = createCredentialUiState.requestDisplayInfo, - disabledProviderList = createCredentialUiState.disabledProviders, - sortedCreateOptionsPairs = - createCredentialUiState.sortedCreateOptionsPairs, - hasRemoteEntry = createCredentialUiState.remoteEntry != null, - onOptionSelected = - viewModel::createFlowOnEntrySelectedFromFirstUseScreen, - onDisabledProvidersSelected = - viewModel::createFlowOnDisabledProvidersSelected, - onMoreOptionsSelected = - viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection, + requestDisplayInfo = createCredentialUiState.requestDisplayInfo, + disabledProviderList = createCredentialUiState + .disabledProviders, + sortedCreateOptionsPairs = + createCredentialUiState.sortedCreateOptionsPairs, + hasRemoteEntry = createCredentialUiState.remoteEntry != null, + onOptionSelected = + viewModel::createFlowOnEntrySelectedFromFirstUseScreen, + onDisabledProvidersSelected = + viewModel::createFlowOnDisabledProvidersSelected, + onMoreOptionsSelected = + viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection, + onLog = { viewModel.logUiEvent(it) }, ) CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard( - requestDisplayInfo = createCredentialUiState.requestDisplayInfo, - enabledProviderList = createCredentialUiState.enabledProviders, - providerInfo = createCredentialUiState.activeEntry?.activeProvider!!, - hasDefaultProvider = createCredentialUiState.hasDefaultProvider, - createOptionInfo = - createCredentialUiState.activeEntry.activeEntryInfo - as CreateOptionInfo, - onOptionSelected = viewModel::createFlowOnEntrySelected, - onConfirm = viewModel::createFlowOnConfirmEntrySelected, - onMoreOptionsSelected = - viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection, + requestDisplayInfo = createCredentialUiState.requestDisplayInfo, + enabledProviderList = createCredentialUiState.enabledProviders, + providerInfo = createCredentialUiState + .activeEntry?.activeProvider!!, + hasDefaultProvider = createCredentialUiState.hasDefaultProvider, + createOptionInfo = + createCredentialUiState.activeEntry.activeEntryInfo + as CreateOptionInfo, + onOptionSelected = viewModel::createFlowOnEntrySelected, + onConfirm = viewModel::createFlowOnConfirmEntrySelected, + onMoreOptionsSelected = + viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection, + onLog = { viewModel.logUiEvent(it) }, ) CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard( - requestDisplayInfo = createCredentialUiState.requestDisplayInfo, - enabledProviderList = createCredentialUiState.enabledProviders, - disabledProviderList = createCredentialUiState.disabledProviders, - sortedCreateOptionsPairs = - createCredentialUiState.sortedCreateOptionsPairs, - hasDefaultProvider = createCredentialUiState.hasDefaultProvider, - isFromProviderSelection = - createCredentialUiState.isFromProviderSelection!!, - onBackProviderSelectionButtonSelected = - viewModel::createFlowOnBackProviderSelectionButtonSelected, - onBackCreationSelectionButtonSelected = - viewModel::createFlowOnBackCreationSelectionButtonSelected, - onOptionSelected = - viewModel::createFlowOnEntrySelectedFromMoreOptionScreen, - onDisabledProvidersSelected = - viewModel::createFlowOnDisabledProvidersSelected, - onRemoteEntrySelected = viewModel::createFlowOnEntrySelected, + requestDisplayInfo = createCredentialUiState.requestDisplayInfo, + enabledProviderList = createCredentialUiState.enabledProviders, + disabledProviderList = createCredentialUiState + .disabledProviders, + sortedCreateOptionsPairs = + createCredentialUiState.sortedCreateOptionsPairs, + hasDefaultProvider = createCredentialUiState.hasDefaultProvider, + isFromProviderSelection = + createCredentialUiState.isFromProviderSelection!!, + onBackProviderSelectionButtonSelected = + viewModel::createFlowOnBackProviderSelectionButtonSelected, + onBackCreationSelectionButtonSelected = + viewModel::createFlowOnBackCreationSelectionButtonSelected, + onOptionSelected = + viewModel::createFlowOnEntrySelectedFromMoreOptionScreen, + onDisabledProvidersSelected = + viewModel::createFlowOnDisabledProvidersSelected, + onRemoteEntrySelected = viewModel::createFlowOnEntrySelected, + onLog = { viewModel.logUiEvent(it) }, ) CreateScreenState.MORE_OPTIONS_ROW_INTRO -> { if (createCredentialUiState.activeEntry == null) { viewModel.onIllegalUiState("Expect active entry to be non-null" + - " upon default provider dialog.") + " upon default provider dialog.") } else { MoreOptionsRowIntroCard( - selectedEntry = createCredentialUiState.activeEntry, - onIllegalScreenState = viewModel::onIllegalUiState, - onChangeDefaultSelected = - viewModel::createFlowOnChangeDefaultSelected, - onUseOnceSelected = viewModel::createFlowOnUseOnceSelected, + selectedEntry = createCredentialUiState.activeEntry, + onIllegalScreenState = viewModel::onIllegalUiState, + onChangeDefaultSelected = + viewModel::createFlowOnChangeDefaultSelected, + onUseOnceSelected = viewModel::createFlowOnUseOnceSelected, + onLog = { viewModel.logUiEvent(it) }, ) } } CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard( - requestDisplayInfo = createCredentialUiState.requestDisplayInfo, - activeRemoteEntry = - createCredentialUiState.activeEntry?.activeEntryInfo!!, - onOptionSelected = viewModel::createFlowOnEntrySelected, - onConfirm = viewModel::createFlowOnConfirmEntrySelected, + requestDisplayInfo = createCredentialUiState.requestDisplayInfo, + activeRemoteEntry = + createCredentialUiState.activeEntry?.activeEntryInfo!!, + onOptionSelected = viewModel::createFlowOnEntrySelected, + onConfirm = viewModel::createFlowOnConfirmEntrySelected, + onLog = { viewModel.logUiEvent(it) }, ) - CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> - MoreAboutPasskeysIntroCard( + CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard( onBackPasskeyIntroButtonSelected = viewModel::createFlowOnBackPasskeyIntroButtonSelected, - ) + onLog = { viewModel.logUiEvent(it) }, + ) } + viewModel.uiMetrics.log( + CreateCredentialEvent + .CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE) } ProviderActivityState.READY_TO_LAUNCH -> { // Launch only once per providerActivityState change so that the provider @@ -167,9 +181,14 @@ fun CreateCredentialScreen( LaunchedEffect(viewModel.uiState.providerActivityState) { viewModel.launchProviderUi(providerActivityLauncher) } + viewModel.uiMetrics.log( + CreateCredentialEvent + .CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH) } ProviderActivityState.PENDING -> { // Hide our content when the provider activity is active. + viewModel.uiMetrics.log( + CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_PENDING) } } }, @@ -181,6 +200,7 @@ fun CreateCredentialScreen( fun PasskeyIntroCard( onConfirm: () -> Unit, onLearnMore: () -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard { item { @@ -245,6 +265,7 @@ fun PasskeyIntroCard( ) } } + onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PASSKEY_INTRO) } @Composable @@ -256,6 +277,7 @@ fun ProviderSelectionCard( onOptionSelected: (ActiveEntry) -> Unit, onDisabledProvidersSelected: () -> Unit, onMoreOptionsSelected: () -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard { item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) } @@ -319,21 +341,23 @@ fun ProviderSelectionCard( } } } + onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_SELECTION) } @Composable fun MoreOptionsSelectionCard( - requestDisplayInfo: RequestDisplayInfo, - enabledProviderList: List<EnabledProviderInfo>, - disabledProviderList: List<DisabledProviderInfo>?, - sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>, - hasDefaultProvider: Boolean, - isFromProviderSelection: Boolean, - onBackProviderSelectionButtonSelected: () -> Unit, - onBackCreationSelectionButtonSelected: () -> Unit, - onOptionSelected: (ActiveEntry) -> Unit, - onDisabledProvidersSelected: () -> Unit, - onRemoteEntrySelected: (BaseEntry) -> Unit, + requestDisplayInfo: RequestDisplayInfo, + enabledProviderList: List<EnabledProviderInfo>, + disabledProviderList: List<DisabledProviderInfo>?, + sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>, + hasDefaultProvider: Boolean, + isFromProviderSelection: Boolean, + onBackProviderSelectionButtonSelected: () -> Unit, + onBackCreationSelectionButtonSelected: () -> Unit, + onOptionSelected: (ActiveEntry) -> Unit, + onDisabledProvidersSelected: () -> Unit, + onRemoteEntrySelected: (BaseEntry) -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard(topAppBar = { MoreOptionTopAppBar( @@ -394,14 +418,16 @@ fun MoreOptionsSelectionCard( } } } + onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_OPTIONS_SELECTION) } @Composable fun MoreOptionsRowIntroCard( - selectedEntry: ActiveEntry, - onIllegalScreenState: (String) -> Unit, - onChangeDefaultSelected: () -> Unit, - onUseOnceSelected: () -> Unit, + selectedEntry: ActiveEntry, + onIllegalScreenState: (String) -> Unit, + onChangeDefaultSelected: () -> Unit, + onUseOnceSelected: () -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { val entryInfo = selectedEntry.activeEntryInfo if (entryInfo !is CreateOptionInfo) { @@ -440,18 +466,20 @@ fun MoreOptionsRowIntroCard( ) } } + onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_OPTIONS_ROW_INTRO) } @Composable fun CreationSelectionCard( - requestDisplayInfo: RequestDisplayInfo, - enabledProviderList: List<EnabledProviderInfo>, - providerInfo: EnabledProviderInfo, - createOptionInfo: CreateOptionInfo, - onOptionSelected: (BaseEntry) -> Unit, - onConfirm: () -> Unit, - onMoreOptionsSelected: () -> Unit, - hasDefaultProvider: Boolean, + requestDisplayInfo: RequestDisplayInfo, + enabledProviderList: List<EnabledProviderInfo>, + providerInfo: EnabledProviderInfo, + createOptionInfo: CreateOptionInfo, + onOptionSelected: (BaseEntry) -> Unit, + onConfirm: () -> Unit, + onMoreOptionsSelected: () -> Unit, + hasDefaultProvider: Boolean, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard { item { @@ -537,14 +565,16 @@ fun CreationSelectionCard( item { BodySmallText(text = createOptionInfo.footerDescription) } } } + onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_CREATION_OPTION_SELECTION) } @Composable fun ExternalOnlySelectionCard( - requestDisplayInfo: RequestDisplayInfo, - activeRemoteEntry: BaseEntry, - onOptionSelected: (BaseEntry) -> Unit, - onConfirm: () -> Unit, + requestDisplayInfo: RequestDisplayInfo, + activeRemoteEntry: BaseEntry, + onOptionSelected: (BaseEntry) -> Unit, + onConfirm: () -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard { item { HeadlineIcon(imageVector = Icons.Outlined.QrCodeScanner) } @@ -572,11 +602,13 @@ fun ExternalOnlySelectionCard( ) } } + onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION) } @Composable fun MoreAboutPasskeysIntroCard( onBackPasskeyIntroButtonSelected: () -> Unit, + onLog: @Composable (UiEventEnum) -> Unit, ) { SheetContainerCard( topAppBar = { @@ -612,6 +644,7 @@ fun MoreAboutPasskeysIntroCard( BodyMediumText(text = stringResource(R.string.seamless_transition_detail)) } } + onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO) } @Composable diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt new file mode 100644 index 000000000000..daa42be020ce --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt @@ -0,0 +1,60 @@ +/* + * 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.credentialmanager.logging + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class CreateCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum { + + @UiEvent(doc = "The create credential bottomsheet became visible on the screen.") + CREDMAN_CREATE_CRED_BOTTOMSHEET(1318), + + @UiEvent(doc = "The provider activity is launched on the screen.") + CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH(1319), + + @UiEvent(doc = "The provider activity is launched and we are waiting for its result. " + + "Contents Hidden.") + CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_PENDING(1320), + + @UiEvent(doc = "The provider activity is not active or ready launched on the screen.") + CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE(1321), + + @UiEvent(doc = "The passkey introduction card is visible on screen.") + CREDMAN_CREATE_CRED_PASSKEY_INTRO(1322), + + @UiEvent(doc = "The provider selection card is visible on screen.") + CREDMAN_CREATE_CRED_PROVIDER_SELECTION(1323), + + @UiEvent(doc = "The creation option selection card is visible on screen.") + CREDMAN_CREATE_CRED_CREATION_OPTION_SELECTION(1324), + + @UiEvent(doc = "The more option selection card is visible on screen.") + CREDMAN_CREATE_CRED_MORE_OPTIONS_SELECTION(1325), + + @UiEvent(doc = "The more options row intro card is visible on screen.") + CREDMAN_CREATE_CRED_MORE_OPTIONS_ROW_INTRO(1326), + + @UiEvent(doc = "The external only selection card is visible on screen.") + CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION(1327), + + @UiEvent(doc = "The more about passkeys intro card is visible on screen.") + CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328); + + override fun getId(): Int { + return this.id + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt new file mode 100644 index 000000000000..4351e8406790 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.logging + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.UiEventLoggerImpl + +class UIMetrics() { + private val INSTANCE_ID_MAX = 1 shl 20 + private val mUiEventLogger: UiEventLogger = UiEventLoggerImpl() + val mInstanceIdSequence: InstanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX) + + var mInstanceId: InstanceId = mInstanceIdSequence.newInstanceId() + + fun resetInstanceId() { + this.mInstanceId = mInstanceIdSequence.newInstanceId() + } + + @Composable + fun log(event: UiEventLogger.UiEventEnum) { + val instanceId: InstanceId = mInstanceId + LaunchedEffect(true) { + mUiEventLogger.log(event, instanceId) + } + } + + @Composable + fun log(event: UiEventLogger.UiEventEnum, packageName: String) { + val instanceId: InstanceId = mInstanceId + LaunchedEffect(true) { + mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId) + } + } + + @Composable + fun log(event: UiEventLogger.UiEventEnum, instanceId: InstanceId, packageName: String) { + LaunchedEffect(true) { + mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId) + } + } +} diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java index 4ed7e19f341d..10b004e1b243 100644 --- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java @@ -29,6 +29,7 @@ import android.os.Looper; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.SparseIntArray; @@ -36,6 +37,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -57,6 +59,7 @@ public final class DeviceStateRotationLockSettingsManager { private final SecureSettings mSecureSettings; private String[] mDeviceStateRotationLockDefaults; private SparseIntArray mDeviceStateRotationLockSettings; + private SparseIntArray mDeviceStateDefaultRotationLockSettings; private SparseIntArray mDeviceStateRotationLockFallbackSettings; private String mLastSettingValue; private List<SettableDeviceState> mSettableDeviceStates; @@ -93,9 +96,7 @@ public final class DeviceStateRotationLockSettingsManager { /** Returns true if device-state based rotation lock settings are enabled. */ public static boolean isDeviceStateRotationLockEnabled(Context context) { return context.getResources() - .getStringArray(R.array.config_perDeviceStateRotationLockDefaults) - .length - > 0; + .getStringArray(R.array.config_perDeviceStateRotationLockDefaults).length > 0; } private void listenForSettingsChange() { @@ -228,6 +229,15 @@ public final class DeviceStateRotationLockSettingsManager { try { key = Integer.parseInt(values[i++]); value = Integer.parseInt(values[i++]); + boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED; + boolean isDefaultValueIgnored = mDeviceStateDefaultRotationLockSettings.get(key) + == DEVICE_STATE_ROTATION_LOCK_IGNORED; + if (isPersistedValueIgnored != isDefaultValueIgnored) { + Log.w(TAG, "Conflict for ignored device state " + key + + ". Falling back on defaults"); + fallbackOnDefaults(); + return; + } mDeviceStateRotationLockSettings.put(key, value); } catch (NumberFormatException e) { Log.wtf(TAG, "Error deserializing one of the saved settings", e); @@ -276,6 +286,9 @@ public final class DeviceStateRotationLockSettingsManager { } private void persistSettingIfChanged(String newSettingValue) { + Log.v(TAG, "persistSettingIfChanged: " + + "last=" + mLastSettingValue + ", " + + "new=" + newSettingValue); if (TextUtils.equals(mLastSettingValue, newSettingValue)) { return; } @@ -288,6 +301,8 @@ public final class DeviceStateRotationLockSettingsManager { private void loadDefaults() { mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length); + mDeviceStateDefaultRotationLockSettings = new SparseIntArray( + mDeviceStateRotationLockDefaults.length); mDeviceStateRotationLockSettings = new SparseIntArray( mDeviceStateRotationLockDefaults.length); mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1); @@ -311,6 +326,7 @@ public final class DeviceStateRotationLockSettingsManager { boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED; mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable)); mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting); + mDeviceStateDefaultRotationLockSettings.put(deviceState, rotationLockSetting); } catch (NumberFormatException e) { Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e); return; @@ -318,6 +334,22 @@ public final class DeviceStateRotationLockSettingsManager { } } + /** Dumps internal state. */ + public void dump(IndentingPrintWriter pw) { + pw.println("DeviceStateRotationLockSettingsManager"); + pw.increaseIndent(); + pw.println("mDeviceStateRotationLockDefaults: " + Arrays.toString( + mDeviceStateRotationLockDefaults)); + pw.println("mDeviceStateDefaultRotationLockSettings: " + + mDeviceStateDefaultRotationLockSettings); + pw.println("mDeviceStateRotationLockSettings: " + mDeviceStateRotationLockSettings); + pw.println("mDeviceStateRotationLockFallbackSettings: " + + mDeviceStateRotationLockFallbackSettings); + pw.println("mSettableDeviceStates: " + mSettableDeviceStates); + pw.println("mLastSettingValue: " + mLastSettingValue); + pw.decreaseIndent(); + } + /** * Called when the persisted settings have changed, requiring a reinitialization of the * in-memory map. @@ -372,5 +404,13 @@ public final class DeviceStateRotationLockSettingsManager { public int hashCode() { return Objects.hash(mDeviceState, mIsSettable); } + + @Override + public String toString() { + return "SettableDeviceState{" + + "mDeviceState=" + mDeviceState + + ", mIsSettable=" + mIsSettable + + '}'; + } } } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index b92b3d665675..8e7d36edb05a 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1648,4 +1648,7 @@ <item>Move right</item> <item>Move up</item> </string-array> + + <!-- Formatting states for the scale of font size, in percent. Double "%" is required to represent the symbol "%". [CHAR LIMIT=20] --> + <string name="font_scale_percentage"> <xliff:g id="percentage">%1$d</xliff:g> %%</string> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java index fb06976ebfe3..3ec5ebad0de6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java @@ -23,6 +23,7 @@ import android.widget.Switch; import androidx.annotation.Keep; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceViewHolder; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -132,6 +133,11 @@ public class PrimarySwitchPreference extends RestrictedPreference { } } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public boolean isSwitchEnabled() { + return mEnableSwitch; + } + /** * If admin is not null, disables the switch. * Otherwise, keep it enabled. diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java index 81006dd6b011..0fa15eb6bc0c 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java @@ -33,7 +33,10 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState; +import com.google.common.truth.Expect; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -45,6 +48,8 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class DeviceStateRotationLockSettingsManagerTest { + @Rule public Expect mExpect = Expect.create(); + @Mock private Context mMockContext; @Mock private Resources mMockResources; @@ -117,4 +122,40 @@ public class DeviceStateRotationLockSettingsManagerTest { new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false) ).inOrder(); } + + @Test + public void persistedInvalidIgnoredState_returnsDefaults() { + when(mMockResources.getStringArray( + R.array.config_perDeviceStateRotationLockDefaults)).thenReturn( + new String[]{"0:1", "1:0:2", "2:2"}); + // Here 2 has IGNORED, and in the defaults 1 has IGNORED. + persistSettings("0:2:2:0:1:2"); + DeviceStateRotationLockSettingsManager manager = + new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings); + + mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(1); + mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(2); + mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(2); + } + + @Test + public void persistedValidValues_returnsPersistedValues() { + when(mMockResources.getStringArray( + R.array.config_perDeviceStateRotationLockDefaults)).thenReturn( + new String[]{"0:1", "1:0:2", "2:2"}); + persistSettings("0:2:1:0:2:1"); + DeviceStateRotationLockSettingsManager manager = + new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings); + + mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(2); + mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(1); + mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(1); + } + + private void persistSettings(String value) { + mFakeSecureSettings.putStringForUser( + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + value, + UserHandle.USER_CURRENT); + } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 17a94b8639d0..296c2ae5cf99 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -419,7 +419,7 @@ class ActivityLaunchAnimator( internal val delegate: AnimationDelegate init { - delegate = AnimationDelegate(controller, callback, launchAnimator, listener) + delegate = AnimationDelegate(controller, callback, listener, launchAnimator) } @BinderThread @@ -446,10 +446,10 @@ class ActivityLaunchAnimator( constructor( private val controller: Controller, private val callback: Callback, - /** The animator to use to animate the window launch. */ - private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR, /** Listener for animation lifecycle events. */ - private val listener: Listener? = null + private val listener: Listener? = null, + /** The animator to use to animate the window launch. */ + private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> { private val launchContainer = controller.launchContainer private val context = launchContainer.context diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index e65c327736e1..8f90724c09b9 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -782,7 +782,7 @@ <!-- Duration in milliseconds of the dream in complications fade-in animation. --> <integer name="config_dreamOverlayInComplicationsDurationMs">250</integer> <!-- Duration in milliseconds of the y-translation animation when entering a dream --> - <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer> + <integer name="config_dreamOverlayInTranslationYDurationMs">1167</integer> <!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index aba3fc4615c9..0f2ce444f225 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -196,9 +196,6 @@ <!-- Increased height of a small notification in the status bar --> <dimen name="notification_min_height_increased">146dp</dimen> - <!-- Increased height of a collapsed media notification in the status bar --> - <dimen name="notification_min_height_media">160dp</dimen> - <!-- Height of a small notification in the status bar which was used before android N --> <dimen name="notification_min_height_legacy">64dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt index 54f933ae6d09..53a421d9eccc 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt @@ -29,6 +29,7 @@ import com.android.systemui.R import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.settings.SystemSettings +import kotlin.math.roundToInt /** The Dialog that contains a seekbar for changing the font size. */ class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) : @@ -56,6 +57,16 @@ class FontScalingDialog(context: Context, private val systemSettings: SystemSett doneButton = requireViewById(com.android.internal.R.id.button1) seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider) + val labelArray = arrayOfNulls<String>(strEntryValues.size) + for (i in strEntryValues.indices) { + labelArray[i] = + context.resources.getString( + com.android.settingslib.R.string.font_scale_percentage, + (strEntryValues[i].toFloat() * 100).roundToInt() + ) + } + seekBarWithIconButtonsView.setProgressStateLabels(labelArray) + seekBarWithIconButtonsView.setMax((strEntryValues).size - 1) val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f) diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 2501be93d189..e049ae09b1de 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -351,9 +351,20 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } private void animateFromMinimized() { - mIsMinimized = false; - setExpandedView(); - animateIn(); + if (mEnterAnimator != null && mEnterAnimator.isRunning()) { + mEnterAnimator.cancel(); + } + mEnterAnimator = mView.getMinimizedFadeoutAnimation(); + mEnterAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mIsMinimized = false; + setExpandedView(); + animateIn(); + } + }); + mEnterAnimator.start(); } private String getAccessibilityAnnouncement(ClipboardModel.Type type) { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java index f372bb4bc7f2..28c57d31a4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java @@ -21,6 +21,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.Nullable; @@ -286,6 +287,20 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mActionChips.clear(); } + Animator getMinimizedFadeoutAnimation() { + ObjectAnimator anim = ObjectAnimator.ofFloat(mMinimizedPreview, "alpha", 1, 0); + anim.setDuration(66); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mMinimizedPreview.setVisibility(View.GONE); + mMinimizedPreview.setAlpha(1); + } + }); + return anim; + } + Animator getEnterAnimation() { if (mAccessibilityManager.isEnabled()) { mDismissButton.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java index 24f6296de122..de3a9901b8c4 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java @@ -45,6 +45,7 @@ public class SeekBarWithIconButtonsView extends LinearLayout { private SeekBar mSeekbar; private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener(); + private String[] mStateLabels = null; public SeekBarWithIconButtonsView(Context context) { this(context, null); @@ -132,6 +133,30 @@ public class SeekBarWithIconButtonsView extends LinearLayout { } /** + * Stores the String array we would like to use for describing the state of seekbar progress + * and updates the state description with current progress. + * + * @param labels The state descriptions to be announced for each progress. + */ + public void setProgressStateLabels(String[] labels) { + mStateLabels = labels; + if (mStateLabels != null) { + setSeekbarStateDescription(); + } + } + + /** + * Sets the state of seekbar based on current progress. The progress of seekbar is + * corresponding to the index of the string array. If the progress is larger than or equals + * to the length of the array, the state description is set to an empty string. + */ + private void setSeekbarStateDescription() { + mSeekbar.setStateDescription( + (mSeekbar.getProgress() < mStateLabels.length) + ? mStateLabels[mSeekbar.getProgress()] : ""); + } + + /** * Sets a onSeekbarChangeListener to the seekbar in the layout. * We update the Start Icon and End Icon if needed when the seekbar progress is changed. */ @@ -173,6 +198,9 @@ public class SeekBarWithIconButtonsView extends LinearLayout { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (mStateLabels != null) { + setSeekbarStateDescription(); + } if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index ca1cef385755..d0a92f0846d0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -43,7 +43,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch @@ -131,9 +130,17 @@ constructor( } } - /** Starts the dream content and dream overlay entry animations. */ + /** + * Starts the dream content and dream overlay entry animations. + * + * @param downwards if true, the entry animation translations downwards into position rather + * than upwards. + */ @JvmOverloads - fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) { + fun startEntryAnimations( + downwards: Boolean, + animatorBuilder: () -> AnimatorSet = { AnimatorSet() } + ) { cancelAnimations() mAnimator = @@ -153,7 +160,7 @@ constructor( interpolator = Interpolators.LINEAR ), translationYAnimator( - from = mDreamInTranslationYDistance.toFloat(), + from = mDreamInTranslationYDistance.toFloat() * (if (downwards) -1 else 1), to = 0f, durationMs = mDreamInTranslationYDurationMs, interpolator = Interpolators.EMPHASIZED_DECELERATE @@ -167,6 +174,71 @@ constructor( } } + /** + * Starts the dream content and dream overlay exit animations. + * + * This should only be used when the low light dream is entering, animations to/from other SysUI + * views is controlled by `transitionViewModel`. + */ + // TODO(b/256916668): integrate with the keyguard transition model once dream surfaces work is + // done. + @JvmOverloads + fun startExitAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }): Animator { + cancelAnimations() + + mAnimator = + animatorBuilder().apply { + playTogether( + translationYAnimator( + from = 0f, + to = -mDreamInTranslationYDistance.toFloat(), + durationMs = mDreamInTranslationYDurationMs, + delayMs = 0, + interpolator = Interpolators.EMPHASIZED + ), + alphaAnimator( + from = + mCurrentAlphaAtPosition.getOrDefault( + key = POSITION_BOTTOM, + defaultValue = 1f + ), + to = 0f, + durationMs = mDreamInComplicationsAnimDurationMs, + delayMs = 0, + positions = POSITION_BOTTOM + ) + .apply { + doOnEnd { + // The logical end of the animation is once the alpha and blur + // animations finish, end the animation so that any listeners are + // notified. The Y translation animation is much longer than all of + // the other animations due to how the spec is defined, but is not + // expected to run to completion. + mAnimator?.end() + } + }, + alphaAnimator( + from = + mCurrentAlphaAtPosition.getOrDefault( + key = POSITION_TOP, + defaultValue = 1f + ), + to = 0f, + durationMs = mDreamInComplicationsAnimDurationMs, + delayMs = 0, + positions = POSITION_TOP + ) + ) + doOnEnd { + mAnimator = null + mOverlayStateController.setExitAnimationsRunning(false) + } + start() + } + mOverlayStateController.setExitAnimationsRunning(true) + return mAnimator as AnimatorSet + } + /** Starts the dream content and dream overlay exit animations. */ fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) { cancelAnimations() @@ -182,19 +254,6 @@ constructor( } } - /** - * Ends the dream content and dream overlay animations, if they're currently running. - * - * @see [AnimatorSet.end] - */ - fun endAnimations() { - mAnimator = - mAnimator?.let { - it.end() - null - } - } - private fun blurAnimator( view: View, fromBlurRadius: Float, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 50cfb6a905c9..4b478cdca9f9 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -23,6 +23,7 @@ import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM; import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP; +import android.animation.Animator; import android.content.res.Resources; import android.os.Handler; import android.util.MathUtils; @@ -31,6 +32,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; +import com.android.dream.lowlight.LowLightTransitionCoordinator; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Main; @@ -54,11 +56,14 @@ import javax.inject.Named; * View controller for {@link DreamOverlayContainerView}. */ @DreamOverlayComponent.DreamOverlayScope -public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> { +public class DreamOverlayContainerViewController extends + ViewController<DreamOverlayContainerView> implements + LowLightTransitionCoordinator.LowLightEnterListener { private final DreamOverlayStatusBarViewController mStatusBarViewController; private final BlurUtils mBlurUtils; private final DreamOverlayAnimationsController mDreamOverlayAnimationsController; private final DreamOverlayStateController mStateController; + private final LowLightTransitionCoordinator mLowLightTransitionCoordinator; private final ComplicationHostViewController mComplicationHostViewController; @@ -143,19 +148,18 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve }; /** - * If true, overlay entry animations should be skipped once. - * - * This is turned on when exiting low light and should be turned off once the entry animations - * are skipped once. + * If {@code true}, the dream has just transitioned from the low light dream back to the user + * dream and we should play an entry animation where the overlay slides in downwards from the + * top instead of the typicla slide in upwards from the bottom. */ - private boolean mSkipEntryAnimations; + private boolean mExitingLowLight; private final DreamOverlayStateController.Callback mDreamOverlayStateCallback = new DreamOverlayStateController.Callback() { @Override public void onExitLowLight() { - mSkipEntryAnimations = true; + mExitingLowLight = true; } }; @@ -165,6 +169,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve ComplicationHostViewController complicationHostViewController, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, DreamOverlayStatusBarViewController statusBarViewController, + LowLightTransitionCoordinator lowLightTransitionCoordinator, BlurUtils blurUtils, @Main Handler handler, @Main Resources resources, @@ -182,6 +187,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve mBlurUtils = blurUtils; mDreamOverlayAnimationsController = animationsController; mStateController = stateController; + mLowLightTransitionCoordinator = lowLightTransitionCoordinator; mBouncerlessScrimController = bouncerlessScrimController; mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback); @@ -208,6 +214,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve mStatusBarViewController.init(); mComplicationHostViewController.init(); mDreamOverlayAnimationsController.init(mView); + mLowLightTransitionCoordinator.setLowLightEnterListener(this); } @Override @@ -219,14 +226,10 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve // Start dream entry animations. Skip animations for low light clock. if (!mStateController.isLowLightActive()) { - mDreamOverlayAnimationsController.startEntryAnimations(); - - if (mSkipEntryAnimations) { - // If we're transitioning from the low light dream back to the user dream, skip the - // overlay animations and show immediately. - mDreamOverlayAnimationsController.endAnimations(); - mSkipEntryAnimations = false; - } + // If this is transitioning from the low light dream to the user dream, the overlay + // should translate in downwards instead of upwards. + mDreamOverlayAnimationsController.startEntryAnimations(mExitingLowLight); + mExitingLowLight = false; } } @@ -310,4 +313,12 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor); } + + @Override + public Animator onBeforeEnterLowLight() { + // Return the animator so that the transition coordinator waits for the overlay exit + // animations to finish before entering low light, as otherwise the default DreamActivity + // animation plays immediately and there's no time for this animation to play. + return mDreamOverlayAnimationsController.startExitAnimations(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java index a2e11b21ea59..24e90f066622 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java @@ -22,14 +22,18 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationModule import android.graphics.Rect; import android.graphics.Region; import android.os.Debug; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.view.View; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.lifecycle.LifecycleOwner; +import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.util.ViewController; +import com.android.systemui.util.settings.SecureSettings; import java.util.Collection; import java.util.HashMap; @@ -54,6 +58,8 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay private final LifecycleOwner mLifecycleOwner; private final ComplicationCollectionViewModel mComplicationCollectionViewModel; private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>(); + @VisibleForTesting + boolean mIsAnimationEnabled; // Whether dream entry animations are finished. private boolean mEntryAnimationsFinished = false; @@ -64,7 +70,8 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay ComplicationLayoutEngine layoutEngine, DreamOverlayStateController dreamOverlayStateController, LifecycleOwner lifecycleOwner, - @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) { + @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel, + SecureSettings secureSettings) { super(view); mLayoutEngine = layoutEngine; mLifecycleOwner = lifecycleOwner; @@ -78,6 +85,10 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay mDreamOverlayStateController.areEntryAnimationsFinished(); } }); + + // Whether animations are enabled. + mIsAnimationEnabled = secureSettings.getFloatForUser( + Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, UserHandle.USER_CURRENT) != 0.0f; } @Override @@ -148,7 +159,7 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay // Complications to be added before dream entry animations are finished are set // to invisible and are animated in. - if (!mEntryAnimationsFinished) { + if (!mEntryAnimationsFinished && mIsAnimationEnabled) { view.setVisibility(View.INVISIBLE); } mComplications.put(id, viewHolder); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 95b37ac5545d..9a6d90a9a88c 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -71,8 +71,12 @@ object Flags { val NOTIFICATION_MEMORY_MONITOR_ENABLED = releasedFlag(112, "notification_memory_monitor_enabled") + /** + * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to + * enable it on release builds. + */ val NOTIFICATION_MEMORY_LOGGING_ENABLED = - unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true) + unreleasedFlag(119, "notification_memory_logging_enabled") // TODO(b/254512731): Tracking Bug @JvmField val NOTIFICATION_DISMISSAL_FADE = releasedFlag(113, "notification_dismissal_fade") @@ -104,6 +108,10 @@ object Flags { val NOTIFICATION_ANIMATE_BIG_PICTURE = releasedFlag(120, "notification_animate_big_picture", teamfood = true) + @JvmField + val ANIMATED_NOTIFICATION_SHADE_INSETS = + unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true) + // 200 - keyguard/lockscreen // ** Flag retired ** // public static final BooleanFlag KEYGUARD_LAYOUT = @@ -469,7 +477,7 @@ object Flags { @Keep @JvmField val ENABLE_PIP_APP_ICON_OVERLAY = - sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = false) + sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true) // 1200 - predictive back @Keep diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java new file mode 100644 index 000000000000..beb725e61e4a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java @@ -0,0 +1,30 @@ +/* + * 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.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DeviceStateAutoRotationLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index ca1ed1f5b0be..d246b35ea397 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -370,6 +370,16 @@ public class LogModule { } /** + * Provides a {@link LogBuffer} for Device State Auto-Rotation logs. + */ + @Provides + @SysUISingleton + @DeviceStateAutoRotationLog + public static LogBuffer provideDeviceStateAutoRotationLogBuffer(LogBufferFactory factory) { + return factory.create("DeviceStateAutoRotationLog", 100); + } + + /** * Provides a {@link LogBuffer} for bluetooth-related logs. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index 680a8b6603d6..67d3be4a3ad2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -197,7 +197,6 @@ constructor( private val configListener = object : ConfigurationController.ConfigurationListener { - var lastOrientation = -1 override fun onDensityOrFontScaleChanged() { // System font changes should only happen when UMO is offscreen or a flicker may @@ -214,13 +213,6 @@ constructor( override fun onConfigChanged(newConfig: Configuration?) { if (newConfig == null) return isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL - val newOrientation = newConfig.orientation - if (lastOrientation != newOrientation) { - // The players actually depend on the orientation possibly, so we have to - // recreate them (at least on large screen devices) - lastOrientation = newOrientation - updatePlayers(recreateMedia = true) - } } override fun onUiModeChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 0788e6172a78..b4724ddebb9a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -154,9 +154,11 @@ constructor( return transitionLayout?.translationY ?: 0.0f } - /** A callback for RTL config changes */ + /** A callback for config changes */ private val configurationListener = object : ConfigurationController.ConfigurationListener { + var lastOrientation = -1 + override fun onConfigChanged(newConfig: Configuration?) { // Because the TransitionLayout is not always attached (and calculates/caches layout // results regardless of attach state), we have to force the layoutDirection of the @@ -169,6 +171,13 @@ constructor( transitionLayout?.layoutDirection = layoutDirection refreshState() } + val newOrientation = newConfig.orientation + if (lastOrientation != newOrientation) { + // Layout dimensions are possibly changing, so we need to update them. (at + // least on large screen devices) + lastOrientation = newOrientation + loadLayoutForType(type) + } } } } @@ -195,13 +204,14 @@ constructor( * The expanded constraint set used to render a expanded player. If it is modified, make sure to * call [refreshState] */ - val collapsedLayout = ConstraintSet() - + var collapsedLayout = ConstraintSet() + @VisibleForTesting set /** * The expanded constraint set used to render a collapsed player. If it is modified, make sure * to call [refreshState] */ - val expandedLayout = ConstraintSet() + var expandedLayout = ConstraintSet() + @VisibleForTesting set /** Whether the guts are visible for the associated player. */ var isGutsVisible = false @@ -483,7 +493,7 @@ constructor( */ fun attach(transitionLayout: TransitionLayout, type: TYPE) = traceSection("MediaViewController#attach") { - updateMediaViewControllerType(type) + loadLayoutForType(type) logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation) this.transitionLayout = transitionLayout layoutController.attach(transitionLayout) @@ -641,7 +651,7 @@ constructor( return result } - private fun updateMediaViewControllerType(type: TYPE) { + private fun loadLayoutForType(type: TYPE) { this.type = type // These XML resources contain ConstraintSets that will apply to this player type's layout diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt index f335733b430c..70040c75d123 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt @@ -2,19 +2,15 @@ package com.android.systemui.navigationbar.gestural import android.content.Context import android.content.res.Configuration -import android.content.res.TypedArray import android.graphics.Canvas import android.graphics.Paint import android.graphics.Path import android.graphics.RectF import android.util.MathUtils.min -import android.util.TypedValue import android.view.View -import androidx.appcompat.view.ContextThemeWrapper import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce -import com.android.internal.R.style.Theme_DeviceDefault import com.android.internal.util.LatencyTracker import com.android.settingslib.Utils import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener @@ -159,26 +155,21 @@ class BackPanel( val isDeviceInNightTheme = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES - val colorControlActivated = ContextThemeWrapper(context, Theme_DeviceDefault) - .run { - val typedValue = TypedValue() - val a: TypedArray = obtainStyledAttributes(typedValue.data, - intArrayOf(android.R.attr.colorControlActivated)) - val color = a.getColor(0, 0) - a.recycle() - color + arrowPaint.color = Utils.getColorAttrDefaultColor(context, + if (isDeviceInNightTheme) { + com.android.internal.R.attr.colorAccentPrimary + } else { + com.android.internal.R.attr.textColorPrimary } + ) - val colorPrimary = - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary) - - arrowPaint.color = Utils.getColorAccentDefaultColor(context) - - arrowBackgroundPaint.color = if (isDeviceInNightTheme) { - colorPrimary - } else { - colorControlActivated - } + arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context, + if (isDeviceInNightTheme) { + com.android.internal.R.attr.materialColorOnSecondary + } else { + com.android.internal.R.attr.colorAccentSecondary + } + ) } inner class AnimatedFloat( @@ -414,9 +405,9 @@ class BackPanel( ) { horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation) scale.updateRestingPosition(restingParams.scale) - arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha) backgroundAlpha.updateRestingPosition(restingParams.backgroundDimens.alpha) + arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha, animate) arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate) arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate) scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index f409b23cf4e2..80ed08c901af 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -55,12 +55,12 @@ private const val PX_PER_MS = 1 internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L private const val MIN_DURATION_CANCELLED_ANIMATION = 200L -private const val MIN_DURATION_COMMITTED_ANIMATION = 200L +private const val MIN_DURATION_COMMITTED_ANIMATION = 120L private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L private const val FAILSAFE_DELAY_MS = 350L -private const val POP_ON_FLING_DELAY = 160L +private const val POP_ON_FLING_DELAY = 140L internal val VIBRATE_ACTIVATED_EFFECT = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK) @@ -148,8 +148,6 @@ class BackPanelController internal constructor( private var gestureSinceActionDown = 0L private var gestureEntryTime = 0L private var gestureActiveTime = 0L - private var gestureInactiveOrEntryTime = 0L - private var gestureArrowStrokeVisibleTime = 0L private val elapsedTimeSinceActionDown get() = SystemClock.uptimeMillis() - gestureSinceActionDown @@ -441,34 +439,44 @@ class BackPanelController internal constructor( updateArrowStateOnMove(yTranslation, xTranslation) - when (currentState) { - GestureState.ACTIVE -> { - stretchActiveBackIndicator(fullScreenProgress(xTranslation)) - } - GestureState.ENTRY -> { - val progress = staticThresholdProgress(xTranslation) - stretchEntryBackIndicator(progress) - - params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let { - mView.popArrowAlpha(0f, it.value) - } - } - GestureState.INACTIVE -> { - val progress = reactivationThresholdProgress(totalTouchDelta) - stretchInactiveBackIndicator(progress) + val gestureProgress = when (currentState) { + GestureState.ACTIVE -> fullScreenProgress(xTranslation) + GestureState.ENTRY -> staticThresholdProgress(xTranslation) + GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDelta) + else -> null + } - params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let { - gestureArrowStrokeVisibleTime = SystemClock.uptimeMillis() - mView.popArrowAlpha(0f, it.value) - } + gestureProgress?.let { + when (currentState) { + GestureState.ACTIVE -> stretchActiveBackIndicator(gestureProgress) + GestureState.ENTRY -> stretchEntryBackIndicator(gestureProgress) + GestureState.INACTIVE -> stretchInactiveBackIndicator(gestureProgress) + else -> {} } - else -> {} } - // set y translation + setArrowStrokeAlpha(gestureProgress) setVerticalTranslation(yOffset) } + private fun setArrowStrokeAlpha(gestureProgress: Float?) { + val strokeAlphaProgress = when (currentState) { + GestureState.ENTRY -> gestureProgress + GestureState.INACTIVE -> gestureProgress + GestureState.ACTIVE, + GestureState.FLUNG, + GestureState.COMMITTED -> 1f + GestureState.CANCELLED, + GestureState.GONE -> 0f + } + + strokeAlphaProgress?.let { progress -> + params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let { + mView.popArrowAlpha(0f, it.value) + } + } + } + private fun setVerticalTranslation(yOffset: Float) { val yTranslation = abs(yOffset) val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f @@ -599,7 +607,7 @@ class BackPanelController internal constructor( private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean { val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop - val flingDistance = abs(endX - startX) + val flingDistance = if (mView.isLeftPanel) endX - startX else startX - endX val isPastFlingVelocity = isDragAwayFromEdge( velocityPxPerSecThreshold = ViewConfiguration.get(context).scaledMinimumFlingVelocity) @@ -764,7 +772,7 @@ class BackPanelController internal constructor( GestureState.ENTRY, GestureState.INACTIVE -> params.entryIndicator.arrowDimens GestureState.ACTIVE -> params.activeIndicator.arrowDimens - GestureState.FLUNG, + GestureState.FLUNG -> params.flungIndicator.arrowDimens GestureState.COMMITTED -> params.committedIndicator.arrowDimens GestureState.CANCELLED -> params.cancelledIndicator.arrowDimens }, @@ -825,7 +833,6 @@ class BackPanelController internal constructor( updateRestingArrowDimens() gestureEntryTime = SystemClock.uptimeMillis() - gestureInactiveOrEntryTime = SystemClock.uptimeMillis() } GestureState.ACTIVE -> { previousXTranslationOnActiveOffset = previousXTranslation @@ -857,7 +864,13 @@ class BackPanelController internal constructor( } GestureState.INACTIVE -> { - gestureInactiveOrEntryTime = SystemClock.uptimeMillis() + + // Typically entering INACTIVE means + // totalTouchDelta <= deactivationSwipeTriggerThreshold + // but because we can also independently enter this state + // if touch Y >> touch X, we force it to deactivationSwipeTriggerThreshold + // so that gesture progress in this state is consistent regardless of entry + totalTouchDelta = params.deactivationSwipeTriggerThreshold val startingVelocity = convertVelocityToSpringStartingVelocity( valueOnFastVelocity = -1.05f, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index 0c0002221244..d46333a50c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -9,8 +9,8 @@ import com.android.systemui.R data class EdgePanelParams(private var resources: Resources) { data class ArrowDimens( - val length: Float = 0f, - val height: Float = 0f, + val length: Float? = 0f, + val height: Float? = 0f, val alpha: Float = 0f, var alphaSpring: SpringForce? = null, val heightSpring: SpringForce? = null, @@ -139,17 +139,17 @@ data class EdgePanelParams(private var resources: Resources) { entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f) entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f) - activeWidthInterpolator = PathInterpolator(.15f, .48f, .46f, .89f) + activeWidthInterpolator = PathInterpolator(.32f, 0f, .16f, .94f) arrowAngleInterpolator = entryWidthInterpolator translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f) farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f) edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f) heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f) - val showArrowOnProgressValue = .2f + val showArrowOnProgressValue = .23f val showArrowOnProgressValueFactor = 1.05f - val entryActiveHorizontalTranslationSpring = createSpring(675f, 0.8f) + val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.8f) val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f) val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f) val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f) @@ -178,7 +178,7 @@ data class EdgePanelParams(private var resources: Resources) { height = getDimen(R.dimen.navigation_edge_entry_background_height), edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners), farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners), - alphaSpring = createSpring(900f, 1f), + alphaSpring = createSpring(1100f, 1f), widthSpring = createSpring(450f, 0.65f), heightSpring = createSpring(1500f, 0.45f), farCornerRadiusSpring = createSpring(300f, 0.5f), @@ -232,7 +232,7 @@ data class EdgePanelParams(private var resources: Resources) { getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners), farCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners), - widthSpring = createSpring(200f, 0.65f), + widthSpring = createSpring(250f, 0.65f), heightSpring = createSpring(1500f, 0.45f), farCornerRadiusSpring = createSpring(200f, 1f), edgeCornerRadiusSpring = createSpring(150f, 0.5f), @@ -244,6 +244,8 @@ data class EdgePanelParams(private var resources: Resources) { arrowDimens = activeIndicator.arrowDimens.copy( lengthSpring = activeCommittedArrowLengthSpring, heightSpring = activeCommittedArrowHeightSpring, + length = null, + height = null, ), backgroundDimens = activeIndicator.backgroundDimens.copy( alpha = 0f, @@ -255,13 +257,15 @@ data class EdgePanelParams(private var resources: Resources) { farCornerRadiusSpring = flungCommittedFarCornerSpring, ), scale = 0.85f, - scaleSpring = createSpring(650f, 1f), + scaleSpring = createSpring(1150f, 1f), ) flungIndicator = committedIndicator.copy( arrowDimens = committedIndicator.arrowDimens.copy( lengthSpring = createSpring(850f, 0.46f), heightSpring = createSpring(850f, 0.46f), + length = activeIndicator.arrowDimens.length, + height = activeIndicator.arrowDimens.height ), backgroundDimens = committedIndicator.backgroundDimens.copy( widthSpring = flungCommittedWidthSpring, diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 7c2536dac56e..d4854e1a7daf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -328,7 +328,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener if (listening) { updateDefaultTileAndIcon(); refreshState(); - if (!mServiceManager.isActiveTile()) { + if (!mServiceManager.isActiveTile() || !isTileReady()) { mServiceManager.setBindRequested(true); mService.onStartListening(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java index ead3b7b1de53..0b4b7c691cfd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java @@ -45,6 +45,7 @@ public class DraggableConstraintLayout extends ConstraintLayout implements ViewTreeObserver.OnComputeInternalInsetsListener { private static final float VELOCITY_DP_PER_MS = 1; + private static final int MAXIMUM_DISMISS_DISTANCE_DP = 400; private final SwipeDismissHandler mSwipeDismissHandler; private final GestureDetector mSwipeDetector; @@ -347,14 +348,18 @@ public class DraggableConstraintLayout extends ConstraintLayout } else { finalX = -1 * getBackgroundRight(); } - float distance = Math.abs(finalX - startX); + float distance = Math.min(Math.abs(finalX - startX), + FloatingWindowUtil.dpToPx(mDisplayMetrics, MAXIMUM_DISMISS_DISTANCE_DP)); + // ensure that view dismisses in the right direction (right in LTR, left in RTL) + float distanceVector = Math.copySign(distance, finalX - startX); anim.addUpdateListener(animation -> { - float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction()); + float translation = MathUtils.lerp( + startX, startX + distanceVector, animation.getAnimatedFraction()); mView.setTranslationX(translation); mView.setAlpha(1 - animation.getAnimatedFraction()); }); - anim.setDuration((long) (distance / Math.abs(velocity))); + anim.setDuration((long) (Math.abs(distance / velocity))); return anim; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index b1987c151e5f..ee9e54a22ddf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -4720,8 +4720,13 @@ public final class NotificationPanelViewController implements Dumpable { gesture possible. */ int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); + if (mTrackingPointer < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } else { + mShadeLog.logMotionEvent(event, "Skipping intercept of multitouch pointer"); + return false; + } } final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index c35c5c522798..77550038c94a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -93,6 +93,16 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN}) public @interface VisibleState { } + /** Returns a human-readable string of {@link VisibleState}. */ + public static String getVisibleStateString(@VisibleState int state) { + switch(state) { + case STATE_ICON: return "ICON"; + case STATE_DOT: return "DOT"; + case STATE_HIDDEN: return "HIDDEN"; + default: return "UNKNOWN"; + } + } + private static final String TAG = "StatusBarIconView"; private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT = new FloatProperty<StatusBarIconView>("iconAppearAmount") { @@ -561,7 +571,8 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @Override public String toString() { return "StatusBarIconView(" - + "slot='" + mSlot + " alpha=" + getAlpha() + " icon=" + mIcon + + "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon + + " visibleState=" + getVisibleStateString(getVisibleState()) + " iconColor=#" + Integer.toHexString(mIconColor) + " notification=" + mNotification + ')'; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt new file mode 100644 index 000000000000..5ce1db2b6acd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.util.ArrayMap +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator +import com.android.systemui.statusbar.notification.collection.render.NotifGroupController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlin.math.max +import kotlin.math.min + +/** A small coordinator which finds, stores, and applies the closest notification time. */ +@CoordinatorScope +class GroupWhenCoordinator +@Inject +constructor( + @Main private val delayableExecutor: DelayableExecutor, + private val systemClock: SystemClock +) : Coordinator { + + private val invalidator = object : Invalidator("GroupWhenCoordinator") {} + private val notificationGroupTimes = ArrayMap<GroupEntry, Long>() + private var cancelInvalidateListRunnable: Runnable? = null + + private val invalidateListRunnable: Runnable = Runnable { + invalidator.invalidateList("future notification invalidation") + } + + override fun attach(pipeline: NotifPipeline) { + pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener) + pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroupListener) + pipeline.addPreRenderInvalidator(invalidator) + } + + private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) { + cancelListInvalidation() + notificationGroupTimes.clear() + + val now = systemClock.currentTimeMillis() + var closestFutureTime = Long.MAX_VALUE + entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry -> + val whenMillis = calculateGroupNotificationTime(groupEntry, now) + notificationGroupTimes[groupEntry] = whenMillis + if (whenMillis > now) { + closestFutureTime = min(closestFutureTime, whenMillis) + } + } + + if (closestFutureTime != Long.MAX_VALUE) { + cancelInvalidateListRunnable = + delayableExecutor.executeDelayed(invalidateListRunnable, closestFutureTime - now) + } + } + + private fun cancelListInvalidation() { + cancelInvalidateListRunnable?.run() + cancelInvalidateListRunnable = null + } + + private fun onAfterRenderGroupListener(group: GroupEntry, controller: NotifGroupController) { + notificationGroupTimes[group]?.let(controller::setNotificationGroupWhen) + } + + private fun calculateGroupNotificationTime( + groupEntry: GroupEntry, + currentTimeMillis: Long + ): Long { + var pastTime = Long.MIN_VALUE + var futureTime = Long.MAX_VALUE + groupEntry.children + .asSequence() + .mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } } + .forEach { time -> + val isInThePast = currentTimeMillis - time > 0 + if (isInThePast) { + pastTime = max(pastTime, time) + } else { + futureTime = min(futureTime, time) + } + } + + if (pastTime == Long.MIN_VALUE && futureTime == Long.MAX_VALUE) { + return checkNotNull(groupEntry.summary).creationTime + } + + return if (futureTime != Long.MAX_VALUE) futureTime else pastTime + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 8a82bcad44e4..6bb5b9218ed7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -31,31 +31,32 @@ interface NotifCoordinators : Coordinator, PipelineDumpable @CoordinatorScope class NotifCoordinatorsImpl @Inject constructor( - notifPipelineFlags: NotifPipelineFlags, - dataStoreCoordinator: DataStoreCoordinator, - hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, - hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, - keyguardCoordinator: KeyguardCoordinator, - rankingCoordinator: RankingCoordinator, - appOpsCoordinator: AppOpsCoordinator, - deviceProvisionedCoordinator: DeviceProvisionedCoordinator, - bubbleCoordinator: BubbleCoordinator, - headsUpCoordinator: HeadsUpCoordinator, - gutsCoordinator: GutsCoordinator, - conversationCoordinator: ConversationCoordinator, - debugModeCoordinator: DebugModeCoordinator, - groupCountCoordinator: GroupCountCoordinator, - mediaCoordinator: MediaCoordinator, - preparationCoordinator: PreparationCoordinator, - remoteInputCoordinator: RemoteInputCoordinator, - rowAppearanceCoordinator: RowAppearanceCoordinator, - stackCoordinator: StackCoordinator, - shadeEventCoordinator: ShadeEventCoordinator, - smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, - viewConfigCoordinator: ViewConfigCoordinator, - visualStabilityCoordinator: VisualStabilityCoordinator, - sensitiveContentCoordinator: SensitiveContentCoordinator, - dismissibilityCoordinator: DismissibilityCoordinator + notifPipelineFlags: NotifPipelineFlags, + dataStoreCoordinator: DataStoreCoordinator, + hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, + hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, + keyguardCoordinator: KeyguardCoordinator, + rankingCoordinator: RankingCoordinator, + appOpsCoordinator: AppOpsCoordinator, + deviceProvisionedCoordinator: DeviceProvisionedCoordinator, + bubbleCoordinator: BubbleCoordinator, + headsUpCoordinator: HeadsUpCoordinator, + gutsCoordinator: GutsCoordinator, + conversationCoordinator: ConversationCoordinator, + debugModeCoordinator: DebugModeCoordinator, + groupCountCoordinator: GroupCountCoordinator, + groupWhenCoordinator: GroupWhenCoordinator, + mediaCoordinator: MediaCoordinator, + preparationCoordinator: PreparationCoordinator, + remoteInputCoordinator: RemoteInputCoordinator, + rowAppearanceCoordinator: RowAppearanceCoordinator, + stackCoordinator: StackCoordinator, + shadeEventCoordinator: ShadeEventCoordinator, + smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, + viewConfigCoordinator: ViewConfigCoordinator, + visualStabilityCoordinator: VisualStabilityCoordinator, + sensitiveContentCoordinator: SensitiveContentCoordinator, + dismissibilityCoordinator: DismissibilityCoordinator ) : NotifCoordinators { private val mCoordinators: MutableList<Coordinator> = ArrayList() @@ -82,6 +83,7 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(debugModeCoordinator) mCoordinators.add(conversationCoordinator) mCoordinators.add(groupCountCoordinator) + mCoordinators.add(groupWhenCoordinator) mCoordinators.add(mediaCoordinator) mCoordinators.add(rowAppearanceCoordinator) mCoordinators.add(stackCoordinator) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt index e2edc01f0d7c..061ef9e9341c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt @@ -20,4 +20,7 @@ package com.android.systemui.statusbar.notification.collection.render interface NotifGroupController { /** Set the number of children that this group would have if not for the 8-child max */ fun setUntruncatedChildCount(untruncatedChildCount: Int) + + /** Set the when value of notification group that reflects most important closest notification time */ + fun setNotificationGroupWhen(whenMillis: Long) }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 2affa77eee04..6deaa23ca20e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -192,7 +192,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private int mMaxSmallHeightBeforeS; private int mMaxSmallHeight; private int mMaxSmallHeightLarge; - private int mMaxSmallHeightMedia; private int mMaxExpandedHeight; private int mIncreasedPaddingBetweenElements; private int mNotificationLaunchHeight; @@ -853,6 +852,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** + * @see NotificationChildrenContainer#setNotificationGroupWhen(long) + */ + public void setNotificationGroupWhen(long whenMillis) { + if (mIsSummaryWithChildren) { + mChildrenContainer.setNotificationGroupWhen(whenMillis); + } else { + Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")" + + " mIsSummaryWithChildren: false" + + " mChildrenContainer has not been inflated yet."); + } + } + + /** * Called after children have been attached to set the expansion states */ public void resetChildSystemExpandedStates() { @@ -1774,8 +1786,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView R.dimen.notification_min_height); mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_min_height_increased); - mMaxSmallHeightMedia = NotificationUtils.getFontScaledHeight(mContext, - R.dimen.notification_min_height_media); mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext, R.dimen.notification_max_height); mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 2dda6fd802e8..dfc80fde3cd2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -349,6 +349,15 @@ public class ExpandableNotificationRowController implements NotifViewController } @Override + public void setNotificationGroupWhen(long whenMillis) { + if (mView.isSummaryWithChildren()) { + mView.setNotificationGroupWhen(whenMillis); + } else { + Log.w(TAG, "Called setNotificationTime(" + whenMillis + ") on a leaf row"); + } + } + + @Override public void setSystemExpanded(boolean systemExpanded) { mView.setSystemExpanded(systemExpanded); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 1f664cb16179..9a777ea6230b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -27,6 +27,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.widget.DateTimeView; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -344,6 +345,21 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple mTransformationHelper.setVisible(visible); } + /*** + * Set Notification when value + * @param whenMillis + */ + public void setNotificationWhen(long whenMillis) { + if (mNotificationHeader == null) { + return; + } + + final View timeView = mNotificationHeader.findViewById(com.android.internal.R.id.time); + + if (timeView instanceof DateTimeView) { + ((DateTimeView) timeView).setTime(whenMillis); + } + } protected void addTransformedViews(View... views) { for (View view : views) { if (view != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 9b93d7b9e1d0..40f55bd3726c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -296,6 +296,19 @@ public class NotificationChildrenContainer extends ViewGroup } /** + * Set the notification time in the group so that the view can show the latest event in the UI + * appropriately. + */ + public void setNotificationGroupWhen(long whenMillis) { + if (mNotificationHeaderWrapper != null) { + mNotificationHeaderWrapper.setNotificationWhen(whenMillis); + } + if (mNotificationHeaderWrapperLowPriority != null) { + mNotificationHeaderWrapperLowPriority.setNotificationWhen(whenMillis); + } + } + + /** * Add a child notification to this view. * * @param row the row to add diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index e09b94bb661d..8d782e1c9fa0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -67,6 +67,7 @@ import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; @@ -199,6 +200,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final boolean mDebugRemoveAnimation; private final boolean mSimplifiedAppearFraction; private final boolean mUseRoundnessSourceTypes; + private boolean mAnimatedInsets; private int mContentHeight; private float mIntrinsicContentHeight; @@ -207,7 +209,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mTopPadding; private boolean mAnimateNextTopPaddingChange; private int mBottomPadding; - private int mBottomInset = 0; + @VisibleForTesting + int mBottomInset = 0; private float mQsExpansionFraction; private final int mSplitShadeMinContentHeight; @@ -388,9 +391,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } }; + private boolean mPulsing; private boolean mScrollable; private View mForcedScroll; + private boolean mIsInsetAnimationRunning; + + private final WindowInsetsAnimation.Callback mInsetsCallback = + new WindowInsetsAnimation.Callback( + WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) { + + @Override + public void onPrepare(WindowInsetsAnimation animation) { + mIsInsetAnimationRunning = true; + } + + @Override + public WindowInsets onProgress(WindowInsets windowInsets, + List<WindowInsetsAnimation> list) { + updateBottomInset(windowInsets); + return windowInsets; + } + + @Override + public void onEnd(WindowInsetsAnimation animation) { + mIsInsetAnimationRunning = false; + } + }; /** * @see #setHideAmount(float, float) @@ -584,6 +611,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION); mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES); + setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS)); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -622,6 +650,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mGroupMembershipManager = Dependency.get(GroupMembershipManager.class); mGroupExpansionManager = Dependency.get(GroupExpansionManager.class); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + if (mAnimatedInsets) { + setWindowInsetsAnimationCallback(mInsetsCallback); + } } /** @@ -690,6 +721,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @VisibleForTesting + void setAnimatedInsetsEnabled(boolean enabled) { + mAnimatedInsets = enabled; + } + + @VisibleForTesting @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void updateFooter() { if (mFooterView == null) { @@ -1781,7 +1817,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return; } mForcedScroll = v; - scrollTo(v); + if (mAnimatedInsets) { + updateForcedScroll(); + } else { + scrollTo(v); + } } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -1813,26 +1853,46 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding()); } + private void updateBottomInset(WindowInsets windowInsets) { + mBottomInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom; + + if (mForcedScroll != null) { + updateForcedScroll(); + } + + int range = getScrollRange(); + if (mOwnScrollY > range) { + setOwnScrollY(range); + } + } + @Override @ShadeViewRefactor(RefactorComponent.COORDINATOR) public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom; + if (!mAnimatedInsets) { + mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom; + } mWaterfallTopInset = 0; final DisplayCutout cutout = insets.getDisplayCutout(); if (cutout != null) { mWaterfallTopInset = cutout.getWaterfallInsets().top; } - - int range = getScrollRange(); - if (mOwnScrollY > range) { - // HACK: We're repeatedly getting staggered insets here while the IME is - // animating away. To work around that we'll wait until things have settled. - removeCallbacks(mReclamp); - postDelayed(mReclamp, 50); - } else if (mForcedScroll != null) { - // The scroll was requested before we got the actual inset - in case we need - // to scroll up some more do so now. - scrollTo(mForcedScroll); + if (mAnimatedInsets && !mIsInsetAnimationRunning) { + // update bottom inset e.g. after rotation + updateBottomInset(insets); + } + if (!mAnimatedInsets) { + int range = getScrollRange(); + if (mOwnScrollY > range) { + // HACK: We're repeatedly getting staggered insets here while the IME is + // animating away. To work around that we'll wait until things have settled. + removeCallbacks(mReclamp); + postDelayed(mReclamp, 50); + } else if (mForcedScroll != null) { + // The scroll was requested before we got the actual inset - in case we need + // to scroll up some more do so now. + scrollTo(mForcedScroll); + } } return insets; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index c72eb054c62c..39b5b5a4cef8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState; +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger; import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView; import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView; @@ -288,10 +289,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da * @param mobileContext possibly mcc/mnc overridden mobile context * @param subId the subscriptionId for this mobile view */ - public void addModernMobileView(Context mobileContext, int subId) { + public void addModernMobileView( + Context mobileContext, + MobileViewLogger mobileViewLogger, + int subId) { Log.d(TAG, "addModernMobileView (subId=" + subId + ")"); ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind( mobileContext, + mobileViewLogger, "mobile", mMobileIconsViewModel.viewModelForSub(subId, mLocation) ); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 11863627218e..04cc8ce792d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -569,7 +569,10 @@ public interface StatusBarIconController { mGroup.addView(view, index, onCreateLayoutParams()); if (mIsInDemoMode) { - mDemoStatusIcons.addModernMobileView(mContext, subId); + mDemoStatusIcons.addModernMobileView( + mContext, + mMobileIconsViewModel.getLogger(), + subId); } return view; @@ -601,6 +604,7 @@ public interface StatusBarIconController { return ModernStatusBarMobileView .constructAndBind( mobileContext, + mMobileIconsViewModel.getLogger(), slot, mMobileIconsViewModel.viewModelForSub(subId, mLocation) ); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java index f6c0da8da8c0..833cb93f62e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java @@ -79,6 +79,18 @@ public class StatusBarIconHolder { private @IconType int mType = TYPE_ICON; private int mTag = 0; + /** Returns a human-readable string representing the given type. */ + public static String getTypeString(@IconType int type) { + switch(type) { + case TYPE_ICON: return "ICON"; + case TYPE_WIFI: return "WIFI_OLD"; + case TYPE_MOBILE: return "MOBILE_OLD"; + case TYPE_MOBILE_NEW: return "MOBILE_NEW"; + case TYPE_WIFI_NEW: return "WIFI_NEW"; + default: return "UNKNOWN"; + } + } + private StatusBarIconHolder() { } @@ -230,4 +242,11 @@ public class StatusBarIconHolder { public int getTag() { return mTag; } + + @Override + public String toString() { + return "StatusBarIconHolder(type=" + getTypeString(mType) + + " tag=" + getTag() + + " visible=" + isVisible() + ")"; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java index 8800b05fadb7..565481a20d95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java @@ -27,6 +27,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** A class holding the list of all the system icons that could be shown in the status bar. */ public class StatusBarIconList { @@ -302,7 +303,7 @@ public class StatusBarIconList { @Override public String toString() { - return String.format("(%s) %s", mName, subSlotsString()); + return String.format("(%s) holder=%s %s", mName, mHolder, subSlotsString()); } private String subSlotsString() { @@ -310,7 +311,10 @@ public class StatusBarIconList { return ""; } - return "" + mSubSlots.size() + " subSlots"; + return "| " + mSubSlots.size() + " subSlots: " + + mSubSlots.stream() + .map(StatusBarIconHolder::toString) + .collect(Collectors.joining("|")); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt index 79c0984d9bf7..d30d0e25bd8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt @@ -22,6 +22,7 @@ import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.AlphaP import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider import com.android.systemui.unfold.SysUIUnfoldScope import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import javax.inject.Inject import kotlin.math.max @@ -29,9 +30,13 @@ import kotlin.math.max @SysUIUnfoldScope class StatusBarMoveFromCenterAnimationController @Inject constructor( private val progressProvider: ScopedUnfoldTransitionProgressProvider, + private val currentActivityTypeProvider: CurrentActivityTypeProvider, windowManager: WindowManager ) { + // Whether we're on home activity. Updated only when the animation starts. + private var isOnHomeActivity: Boolean? = null + private val transitionListener = TransitionListener() private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator( windowManager, @@ -60,6 +65,10 @@ class StatusBarMoveFromCenterAnimationController @Inject constructor( } private inner class TransitionListener : TransitionProgressListener { + override fun onTransitionStarted() { + isOnHomeActivity = currentActivityTypeProvider.isHomeActivity + } + override fun onTransitionProgress(progress: Float) { moveFromCenterAnimator.onTransitionProgress(progress) } @@ -68,11 +77,23 @@ class StatusBarMoveFromCenterAnimationController @Inject constructor( // Reset translations when transition is stopped/cancelled // (e.g. the transition could be cancelled mid-way when rotating the screen) moveFromCenterAnimator.onTransitionProgress(1f) + isOnHomeActivity = null } } - private class StatusBarIconsAlphaProvider : AlphaProvider { + + /** + * In certain cases, an alpha is applied based on the progress. + * + * This mainly happens to hide the statusbar during the unfold animation while on apps, as the + * bounds of the app "collapse" to the center, but the statusbar doesn't. + * While on launcher, this alpha is not applied. + */ + private inner class StatusBarIconsAlphaProvider : AlphaProvider { override fun getAlpha(progress: Float): Float { + if (isOnHomeActivity == true) { + return 1.0f + } return max( 0f, (progress - ICONS_START_APPEARING_PROGRESS) / (1 - ICONS_START_APPEARING_PROGRESS) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt new file mode 100644 index 000000000000..e594a8a5efd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt @@ -0,0 +1,25 @@ +/* + * 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.pipeline.dagger + +import javax.inject.Qualifier + +/** Logs for changes with the new mobile views. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class MobileViewLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 44647515a6e5..adfea80715a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -148,5 +148,19 @@ abstract class StatusBarPipelineModule { fun provideMobileInputLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("MobileInputLog", 100) } + + @Provides + @SysUISingleton + @MobileViewLog + fun provideMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer { + return factory.create("MobileViewLog", 100) + } + + @Provides + @SysUISingleton + @VerboseMobileViewLog + fun provideVerboseMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer { + return factory.create("VerboseMobileViewLog", 100) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt new file mode 100644 index 000000000000..b98789807dd3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt @@ -0,0 +1,25 @@ +/* + * 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.pipeline.dagger + +import javax.inject.Qualifier + +/** Logs for **verbose** changes with the new mobile views. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class VerboseMobileViewLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 3cbd2b76c248..159f689de370 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.mobile.shared +package com.android.systemui.statusbar.pipeline.mobile.data import android.net.Network import android.net.NetworkCapabilities @@ -133,24 +133,6 @@ constructor( ) } - fun logUiAdapterSubIdsUpdated(subs: List<Int>) { - buffer.log( - TAG, - LogLevel.INFO, - { str1 = subs.toString() }, - { "Sub IDs in MobileUiAdapter updated internally: $str1" }, - ) - } - - fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { - buffer.log( - TAG, - LogLevel.INFO, - { str1 = subs.toString() }, - { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" }, - ) - } - fun logCarrierConfigChanged(subId: Int) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt index bb3b9b2166c3..efdce062bb37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt @@ -30,8 +30,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index 96b96f14d6aa..e182bc66081a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -36,6 +36,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType @@ -47,7 +48,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameMo import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 53a208cd171e..f97e41c018f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -45,11 +45,11 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt index da63ab10f733..075e6ec11ae7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt @@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import java.io.PrintWriter import javax.inject.Inject @@ -55,17 +54,14 @@ constructor( interactor: MobileIconsInteractor, private val iconController: StatusBarIconController, private val iconsViewModelFactory: MobileIconsViewModel.Factory, - private val logger: MobileInputLogger, + private val logger: MobileViewLogger, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { private val mobileSubIds: Flow<List<Int>> = - interactor.filteredSubscriptions - .mapLatest { subscriptions -> - subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } - } - .distinctUntilChanged() - .onEach { logger.logUiAdapterSubIdsUpdated(it) } + interactor.filteredSubscriptions.mapLatest { subscriptions -> + subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } + } /** * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is @@ -75,7 +71,10 @@ constructor( * NOTE: this should go away as the view presenter learns more about this data pipeline */ private val mobileSubIdsState: StateFlow<List<Int>> = - mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + mobileSubIds + .distinctUntilChanged() + .onEach { logger.logUiAdapterSubIdsUpdated(it) } + .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */ val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt new file mode 100644 index 000000000000..90dff23c637c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt @@ -0,0 +1,118 @@ +/* + * 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.pipeline.mobile.ui + +import android.view.View +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel +import java.io.PrintWriter +import javax.inject.Inject + +/** Logs for changes with the new mobile views. */ +@SysUISingleton +class MobileViewLogger +@Inject +constructor( + @MobileViewLog private val buffer: LogBuffer, + dumpManager: DumpManager, +) : Dumpable { + init { + dumpManager.registerNormalDumpable(this) + } + + private val collectionStatuses = mutableMapOf<String, Boolean>() + + fun logUiAdapterSubIdsUpdated(subs: List<Int>) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = subs.toString() }, + { "Sub IDs in MobileUiAdapter updated internally: $str1" }, + ) + } + + fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = subs.toString() }, + { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" }, + ) + } + + fun logNewViewBinding(view: View, viewModel: LocationBasedMobileViewModel) { + buffer.log( + TAG, + LogLevel.INFO, + { + str1 = view.getIdForLogging() + str2 = viewModel.getIdForLogging() + str3 = viewModel.locationName + }, + { "New view binding. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, + ) + } + + fun logCollectionStarted(view: View, viewModel: LocationBasedMobileViewModel) { + collectionStatuses[view.getIdForLogging()] = true + buffer.log( + TAG, + LogLevel.INFO, + { + str1 = view.getIdForLogging() + str2 = viewModel.getIdForLogging() + str3 = viewModel.locationName + }, + { "Collection started. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, + ) + } + + fun logCollectionStopped(view: View, viewModel: LocationBasedMobileViewModel) { + collectionStatuses[view.getIdForLogging()] = false + buffer.log( + TAG, + LogLevel.INFO, + { + str1 = view.getIdForLogging() + str2 = viewModel.getIdForLogging() + str3 = viewModel.locationName + }, + { "Collection stopped. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" }, + ) + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("Collection statuses per view:---") + collectionStatuses.forEach { viewId, isCollecting -> + pw.println("viewId=$viewId, isCollecting=$isCollecting") + } + } + + companion object { + fun Any.getIdForLogging(): String { + // The identityHashCode is guaranteed to be constant for the lifetime of the object. + return Integer.toHexString(System.identityHashCode(this)) + } + } +} + +private const val TAG = "MobileViewLogger" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt new file mode 100644 index 000000000000..f67bc8f14447 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt @@ -0,0 +1,76 @@ +/* + * 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.pipeline.mobile.ui + +import android.view.View +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging +import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel +import javax.inject.Inject + +/** + * Logs for **verbose** changes with the new mobile views. + * + * This is a hopefully temporary log until we resolve some open bugs (b/267236367, b/269565345, + * b/270300839). + */ +@SysUISingleton +class VerboseMobileViewLogger +@Inject +constructor( + @VerboseMobileViewLog private val buffer: LogBuffer, +) { + fun logBinderReceivedSignalIcon(parentView: View, subId: Int, icon: SignalIconModel) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = parentView.getIdForLogging() + int1 = subId + int2 = icon.level + bool1 = icon.showExclamationMark + }, + { + "Binder[subId=$int1, viewId=$str1] received new signal icon: " + + "level=$int2 showExclamation=$bool1" + }, + ) + } + + fun logBinderReceivedNetworkTypeIcon(parentView: View, subId: Int, icon: Icon.Resource?) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = parentView.getIdForLogging() + int1 = subId + bool1 = icon != null + int2 = icon?.res ?: -1 + }, + { + "Binder[subId=$int1, viewId=$str1] received new network type icon: " + + if (bool1) "resId=$int2" else "null" + }, + ) + } +} + +private const val TAG = "VerboseMobileViewLogger" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index db585e68d185..5b7d45b55c5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -36,8 +36,10 @@ import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -48,6 +50,7 @@ object MobileIconBinder { fun bind( view: ViewGroup, viewModel: LocationBasedMobileViewModel, + logger: MobileViewLogger, ): ModernStatusBarViewBinding { val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group) val activityContainer = view.requireViewById<View>(R.id.inout_container) @@ -70,8 +73,13 @@ object MobileIconBinder { val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) + var isCollecting: Boolean = false + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { + logger.logCollectionStarted(view, viewModel) + isCollecting = true + launch { visibilityState.collect { state -> when (state) { @@ -96,6 +104,11 @@ object MobileIconBinder { // Set the icon for the triangle launch { viewModel.icon.distinctUntilChanged().collect { icon -> + viewModel.verboseLogger?.logBinderReceivedSignalIcon( + view, + viewModel.subscriptionId, + icon, + ) mobileDrawable.level = SignalDrawable.getState( icon.level, @@ -114,6 +127,11 @@ object MobileIconBinder { // Set the network type icon launch { viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId -> + viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon( + view, + viewModel.subscriptionId, + dataTypeId, + ) dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) } networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE } @@ -150,6 +168,13 @@ object MobileIconBinder { } launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } } + + try { + awaitCancellation() + } finally { + isCollecting = false + logger.logCollectionStopped(view, viewModel) + } } } @@ -175,6 +200,10 @@ object MobileIconBinder { } decorTint.value = newTint } + + override fun isCollecting(): Boolean { + return isCollecting + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt index ed9a1884a7b4..4144293d5ccd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt @@ -20,6 +20,8 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import com.android.systemui.R +import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView @@ -31,6 +33,15 @@ class ModernStatusBarMobileView( var subId: Int = -1 + override fun toString(): String { + return "ModernStatusBarMobileView(" + + "slot='$slot', " + + "subId=$subId, " + + "isCollecting=${binding.isCollecting()}, " + + "visibleState=${getVisibleStateString(visibleState)}); " + + "viewString=${super.toString()}" + } + companion object { /** @@ -40,6 +51,7 @@ class ModernStatusBarMobileView( @JvmStatic fun constructAndBind( context: Context, + logger: MobileViewLogger, slot: String, viewModel: LocationBasedMobileViewModel, ): ModernStatusBarMobileView { @@ -48,7 +60,8 @@ class ModernStatusBarMobileView( as ModernStatusBarMobileView) .also { it.subId = viewModel.subscriptionId - it.initView(slot) { MobileIconBinder.bind(it, viewModel) } + it.initView(slot) { MobileIconBinder.bind(it, viewModel, logger) } + logger.logNewViewBinding(it, viewModel) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt index 8e103f7bee2f..f775940140cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import android.graphics.Color import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger /** * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This @@ -26,11 +27,15 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags * * @param commonImpl for convenience, this class wraps a base interface that can provides all of the * common implementations between locations. See [MobileIconViewModel] + * @property locationName the name of the location of this VM, used for logging. + * @property verboseLogger an optional logger to log extremely verbose view updates. */ abstract class LocationBasedMobileViewModel( val commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, debugTint: Int, + val locationName: String, + val verboseLogger: VerboseMobileViewLogger?, ) : MobileIconViewModelCommon by commonImpl { val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring() @@ -45,11 +50,16 @@ abstract class LocationBasedMobileViewModel( fun viewModelForLocation( commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, + verboseMobileViewLogger: VerboseMobileViewLogger, loc: StatusBarLocation, ): LocationBasedMobileViewModel = when (loc) { StatusBarLocation.HOME -> - HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags) + HomeMobileIconViewModel( + commonImpl, + statusBarPipelineFlags, + verboseMobileViewLogger, + ) StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags) StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags) @@ -60,20 +70,41 @@ abstract class LocationBasedMobileViewModel( class HomeMobileIconViewModel( commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, + verboseMobileViewLogger: VerboseMobileViewLogger, ) : MobileIconViewModelCommon, - LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN) + LocationBasedMobileViewModel( + commonImpl, + statusBarPipelineFlags, + debugTint = Color.CYAN, + locationName = "Home", + verboseMobileViewLogger, + ) class QsMobileIconViewModel( commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, ) : MobileIconViewModelCommon, - LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN) + LocationBasedMobileViewModel( + commonImpl, + statusBarPipelineFlags, + debugTint = Color.GREEN, + locationName = "QS", + // Only do verbose logging for the Home location. + verboseLogger = null, + ) class KeyguardMobileIconViewModel( commonImpl: MobileIconViewModelCommon, statusBarPipelineFlags: StatusBarPipelineFlags, ) : MobileIconViewModelCommon, - LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA) + LocationBasedMobileViewModel( + commonImpl, + statusBarPipelineFlags, + debugTint = Color.MAGENTA, + locationName = "Keyguard", + // Only do verbose logging for the Home location. + verboseLogger = null, + ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 049627899eff..dbb534b24471 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -49,7 +49,7 @@ interface MobileIconViewModelCommon { val contentDescription: Flow<ContentDescription> val roaming: Flow<Boolean> /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ - val networkTypeIcon: Flow<Icon?> + val networkTypeIcon: Flow<Icon.Resource?> val activityInVisible: Flow<Boolean> val activityOutVisible: Flow<Boolean> val activityContainerVisible: Flow<Boolean> @@ -161,7 +161,7 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) - override val networkTypeIcon: Flow<Icon?> = + override val networkTypeIcon: Flow<Icon.Resource?> = combine( iconInteractor.networkTypeIconGroup, showNetworkTypeIcon, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 8cb52af336da..2b90065284d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -23,6 +23,8 @@ import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject @@ -39,6 +41,8 @@ class MobileIconsViewModel @Inject constructor( val subscriptionIdsFlow: StateFlow<List<Int>>, + val logger: MobileViewLogger, + private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, private val airplaneModeInteractor: AirplaneModeInteractor, private val constants: ConnectivityConstants, @@ -66,6 +70,7 @@ constructor( return LocationBasedMobileViewModel.viewModelForLocation( common, statusBarPipelineFlags, + verboseLogger, location, ) } @@ -79,6 +84,8 @@ constructor( class Factory @Inject constructor( + private val logger: MobileViewLogger, + private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, private val airplaneModeInteractor: AirplaneModeInteractor, private val constants: ConnectivityConstants, @@ -88,6 +95,8 @@ constructor( fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel { return MobileIconsViewModel( subscriptionIdsFlow, + logger, + verboseLogger, interactor, airplaneModeInteractor, constants, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt index f67876b50233..81f8683411ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt @@ -37,4 +37,7 @@ interface ModernStatusBarViewBinding { /** Notifies that the decor tint has been updated (used only for the dot). */ fun onDecorTintChanged(newTint: Int) + + /** Returns true if the binding between the view and view-model is currently collecting. */ + fun isCollecting(): Boolean } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt index b1e28129a690..1a1340484bfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt @@ -36,7 +36,7 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) : BaseStatusBarFrameLayout(context, attrs) { private lateinit var slot: String - private lateinit var binding: ModernStatusBarViewBinding + internal lateinit var binding: ModernStatusBarViewBinding @StatusBarIconView.VisibleState private var iconVisibleState: Int = STATE_HIDDEN diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index 2aff12c8721d..9e8c814ca2a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarV import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged @@ -74,8 +75,12 @@ object WifiViewBinder { val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) + var isCollecting: Boolean = false + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { + isCollecting = true + launch { visibilityState.collect { visibilityState -> groupView.isVisible = visibilityState == STATE_ICON @@ -127,6 +132,12 @@ object WifiViewBinder { airplaneSpacer.isVisible = visible } } + + try { + awaitCancellation() + } finally { + isCollecting = false + } } } @@ -152,6 +163,10 @@ object WifiViewBinder { } decorTint.value = newTint } + + override fun isCollecting(): Boolean { + return isCollecting + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt index 7a734862fe1b..f23e10287164 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt @@ -21,6 +21,7 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import com.android.systemui.R +import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel @@ -33,6 +34,15 @@ class ModernStatusBarWifiView( context: Context, attrs: AttributeSet?, ) : ModernStatusBarView(context, attrs) { + + override fun toString(): String { + return "ModernStatusBarWifiView(" + + "slot='$slot', " + + "isCollecting=${binding.isCollecting()}, " + + "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " + + "viewString=${super.toString()}" + } + companion object { /** * Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and @@ -45,12 +55,9 @@ class ModernStatusBarWifiView( slot: String, wifiViewModel: LocationBasedWifiViewModel, ): ModernStatusBarWifiView { - return ( - LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) - as ModernStatusBarWifiView - ).also { - it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } - } + return (LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null) + as ModernStatusBarWifiView) + .also { it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java index 7acdaffb48c4..01fabcc8bc1e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -22,13 +22,18 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED import android.annotation.Nullable; import android.hardware.devicestate.DeviceStateManager; import android.os.Trace; -import android.util.Log; +import android.util.IndentingPrintWriter; + +import androidx.annotation.NonNull; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; +import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dump.DumpManager; import com.android.systemui.util.wrapper.RotationPolicyWrapper; +import java.io.PrintWriter; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -39,19 +44,19 @@ import javax.inject.Inject; */ @SysUISingleton public final class DeviceStateRotationLockSettingController - implements Listenable, RotationLockController.RotationLockControllerCallback { - - private static final String TAG = "DSRotateLockSettingCon"; + implements Listenable, RotationLockController.RotationLockControllerCallback, Dumpable { private final RotationPolicyWrapper mRotationPolicyWrapper; private final DeviceStateManager mDeviceStateManager; private final Executor mMainExecutor; private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager; + private final DeviceStateRotationLockSettingControllerLogger mLogger; // On registration for DeviceStateCallback, we will receive a callback with the current state // and this will be initialized. private int mDeviceState = -1; - @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; + @Nullable + private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener mDeviceStateRotationLockSettingsListener; @@ -60,21 +65,27 @@ public final class DeviceStateRotationLockSettingController RotationPolicyWrapper rotationPolicyWrapper, DeviceStateManager deviceStateManager, @Main Executor executor, - DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) { + DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager, + DeviceStateRotationLockSettingControllerLogger logger, + DumpManager dumpManager) { mRotationPolicyWrapper = rotationPolicyWrapper; mDeviceStateManager = deviceStateManager; mMainExecutor = executor; mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager; + mLogger = logger; + dumpManager.registerDumpable(this); } @Override public void setListening(boolean listening) { + mLogger.logListeningChange(listening); if (listening) { // Note that this is called once with the initial state of the device, even if there // is no user action. mDeviceStateCallback = this::updateDeviceState; mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback); - mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState); + mDeviceStateRotationLockSettingsListener = () -> + readPersistedSetting("deviceStateRotationLockChange", mDeviceState); mDeviceStateRotationLockSettingsManager.registerListener( mDeviceStateRotationLockSettingsListener); } else { @@ -89,35 +100,28 @@ public final class DeviceStateRotationLockSettingController } @Override - public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) { - if (mDeviceState == -1) { - Log.wtf(TAG, "Device state was not initialized."); + public void onRotationLockStateChanged(boolean newRotationLocked, boolean affordanceVisible) { + int deviceState = mDeviceState; + boolean currentRotationLocked = mDeviceStateRotationLockSettingsManager + .isRotationLocked(deviceState); + mLogger.logRotationLockStateChanged(deviceState, newRotationLocked, currentRotationLocked); + if (deviceState == -1) { return; } - - if (rotationLocked - == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) { - Log.v(TAG, "Rotation lock same as the current setting, no need to update."); + if (newRotationLocked == currentRotationLocked) { return; } - - saveNewRotationLockSetting(rotationLocked); + saveNewRotationLockSetting(newRotationLocked); } private void saveNewRotationLockSetting(boolean isRotationLocked) { - Log.v( - TAG, - "saveNewRotationLockSetting [state=" - + mDeviceState - + "] [isRotationLocked=" - + isRotationLocked - + "]"); - - mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked); + int deviceState = mDeviceState; + mLogger.logSaveNewRotationLockSetting(isRotationLocked, deviceState); + mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked); } private void updateDeviceState(int state) { - Log.v(TAG, "updateDeviceState [state=" + state + "]"); + mLogger.logUpdateDeviceState(mDeviceState, state); if (Trace.isEnabled()) { Trace.traceBegin( Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]"); @@ -127,22 +131,26 @@ public final class DeviceStateRotationLockSettingController return; } - readPersistedSetting(state); + readPersistedSetting("updateDeviceState", state); } finally { Trace.endSection(); } } - private void readPersistedSetting(int state) { + private void readPersistedSetting(String caller, int state) { int rotationLockSetting = mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state); + boolean shouldBeLocked = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED; + boolean isLocked = mRotationPolicyWrapper.isRotationLocked(); + + mLogger.readPersistedSetting(caller, state, rotationLockSetting, shouldBeLocked, isLocked); + if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { // This should not happen. Device states that have an ignored setting, should also // specify a fallback device state which is not ignored. // We won't handle this device state. The same rotation lock setting as before should // apply and any changes to the rotation lock setting will be written for the previous // valid device state. - Log.w(TAG, "Missing fallback. Ignoring new device state: " + state); return; } @@ -150,9 +158,18 @@ public final class DeviceStateRotationLockSettingController mDeviceState = state; // Update the rotation policy, if needed, for this new device state - boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED; - if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) { - mRotationPolicyWrapper.setRotationLock(newRotationLockSetting); + if (shouldBeLocked != isLocked) { + mRotationPolicyWrapper.setRotationLock(shouldBeLocked); } } + + @Override + public void dump(@NonNull PrintWriter printWriter, @NonNull String[] args) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter); + mDeviceStateRotationLockSettingsManager.dump(pw); + pw.println("DeviceStateRotationLockSettingController"); + pw.increaseIndent(); + pw.println("mDeviceState: " + mDeviceState); + pw.decreaseIndent(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt new file mode 100644 index 000000000000..aa502bc48149 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import android.content.Context +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED +import com.android.internal.R +import com.android.systemui.log.dagger.DeviceStateAutoRotationLog +import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.VERBOSE +import javax.inject.Inject + +class DeviceStateRotationLockSettingControllerLogger +@Inject +constructor(@DeviceStateAutoRotationLog private val logBuffer: LogBuffer, context: Context) { + + private val foldedStates = context.resources.getIntArray(R.array.config_foldedDeviceStates) + private val halfFoldedStates = + context.resources.getIntArray(R.array.config_halfFoldedDeviceStates) + private val unfoldedStates = context.resources.getIntArray(R.array.config_openDeviceStates) + + fun logListeningChange(listening: Boolean) { + logBuffer.log(TAG, VERBOSE, { bool1 = listening }, { "setListening: $bool1" }) + } + + fun logRotationLockStateChanged( + state: Int, + newRotationLocked: Boolean, + currentRotationLocked: Boolean + ) { + logBuffer.log( + TAG, + VERBOSE, + { + int1 = state + bool1 = newRotationLocked + bool2 = currentRotationLocked + }, + { + "onRotationLockStateChanged: " + + "state=$int1 [${int1.toDevicePostureString()}], " + + "newRotationLocked=$bool1, " + + "currentRotationLocked=$bool2" + } + ) + } + + fun logSaveNewRotationLockSetting(isRotationLocked: Boolean, state: Int) { + logBuffer.log( + TAG, + VERBOSE, + { + bool1 = isRotationLocked + int1 = state + }, + { "saveNewRotationLockSetting: isRotationLocked=$bool1, state=$int1" } + ) + } + + fun logUpdateDeviceState(currentState: Int, newState: Int) { + logBuffer.log( + TAG, + VERBOSE, + { + int1 = currentState + int2 = newState + }, + { + "updateDeviceState: " + + "current=$int1 [${int1.toDevicePostureString()}], " + + "new=$int2 [${int2.toDevicePostureString()}]" + } + ) + } + + fun readPersistedSetting( + caller: String, + state: Int, + rotationLockSetting: Int, + shouldBeLocked: Boolean, + isLocked: Boolean + ) { + logBuffer.log( + TAG, + VERBOSE, + { + str1 = caller + int1 = state + int2 = rotationLockSetting + bool1 = shouldBeLocked + bool2 = isLocked + }, + { + "readPersistedSetting: " + + "caller=$str1, " + + "state=$int1 [${int1.toDevicePostureString()}], " + + "rotationLockSettingForState: ${int2.toRotationLockSettingString()}, " + + "shouldBeLocked=$bool1, " + + "isLocked=$bool2" + } + ) + } + + private fun Int.toDevicePostureString(): String { + return when (this) { + in foldedStates -> "Folded" + in unfoldedStates -> "Unfolded" + in halfFoldedStates -> "Half-Folded" + -1 -> "Uninitialized" + else -> "Unknown" + } + } +} + +private fun Int.toRotationLockSettingString(): String { + return when (this) { + DEVICE_STATE_ROTATION_LOCK_IGNORED -> "IGNORED" + DEVICE_STATE_ROTATION_LOCK_LOCKED -> "LOCKED" + DEVICE_STATE_ROTATION_LOCK_UNLOCKED -> "UNLOCKED" + else -> "Unknown" + } +} + +private const val TAG = "DSRotateLockSettingCon" diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java index 35039026fe9e..9d16185e75c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java @@ -45,8 +45,8 @@ import org.mockito.MockitoAnnotations; public class BroadcastDialogTest extends SysuiTestCase { private static final String CURRENT_BROADCAST_APP = "Music"; - private static final String SWITCH_APP = "Files by Google"; - private static final String TEST_PACKAGE = "com.google.android.apps.nbu.files"; + private static final String SWITCH_APP = "System UI"; + private static final String TEST_PACKAGE = "com.android.systemui"; private BroadcastDialog mBroadcastDialog; private View mDialogView; private TextView mTitle; @@ -59,6 +59,7 @@ public class BroadcastDialogTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class), CURRENT_BROADCAST_APP, TEST_PACKAGE, mock(UiEventLogger.class)); + mBroadcastDialog.show(); mDialogView = mBroadcastDialog.mDialogView; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index c2fb904f64ad..ffd75fb9bbc6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.RemoteAction; import android.content.ClipData; import android.content.ClipDescription; @@ -101,6 +102,9 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor; private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks; + @Captor + private ArgumentCaptor<AnimatorListenerAdapter> mAnimatorArgumentCaptor; + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @Before @@ -478,12 +482,16 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { when(mClipboardOverlayWindow.getWindowInsets()).thenReturn( getImeInsets(new Rect(0, 0, 0, 1))); mOverlayController.setClipData(mSampleClipData, ""); + Animator mockFadeoutAnimator = Mockito.mock(Animator.class); + when(mClipboardOverlayView.getMinimizedFadeoutAnimation()).thenReturn(mockFadeoutAnimator); verify(mClipboardOverlayView).setMinimized(true); verify(mClipboardOverlayView, never()).setMinimized(false); verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean()); mCallbacks.onMinimizedViewTapped(); + verify(mockFadeoutAnimator).addListener(mAnimatorArgumentCaptor.capture()); + mAnimatorArgumentCaptor.getValue().onAnimationEnd(mockFadeoutAnimator); verify(mClipboardOverlayView).setMinimized(false); verify(mClipboardOverlayView).showTextPreview("Test Item", false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java index eafe727ee7dc..afd9be5787c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java @@ -107,4 +107,32 @@ public class SeekBarWithIconButtonsViewTest extends SysuiTestCase { assertThat(mSeekbar.getProgress()).isEqualTo(0); } + + @Test + public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() { + String[] stateLabels = new String[]{"1", "2", "3", "4", "5"}; + mIconDiscreteSliderLinearLayout.setMax(stateLabels.length); + mIconDiscreteSliderLinearLayout.setProgress(1); + mIconDiscreteSliderLinearLayout.setProgressStateLabels(stateLabels); + + final int currentProgress = mSeekbar.getProgress(); + final CharSequence stateDescription = mSeekbar.getStateDescription(); + + assertThat(currentProgress).isEqualTo(1); + assertThat(stateDescription).isEqualTo(stateLabels[currentProgress]); + } + + @Test + public void setProgressStateLabels_progressChanged_getExpectedStateDescription() { + String[] stateLabels = new String[]{"1", "2", "3", "4", "5"}; + mIconDiscreteSliderLinearLayout.setMax(stateLabels.length); + mIconDiscreteSliderLinearLayout.setProgressStateLabels(stateLabels); + mIconDiscreteSliderLinearLayout.setProgress(1); + + final int currentProgress = mSeekbar.getProgress(); + final CharSequence stateDescription = mSeekbar.getStateDescription(); + + assertThat(currentProgress).isEqualTo(1); + assertThat(stateDescription).isEqualTo(stateLabels[currentProgress]); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index 6c23254941a8..0a9470617a5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -1,6 +1,8 @@ package com.android.systemui.dreams +import android.animation.Animator import android.animation.AnimatorSet +import android.animation.ValueAnimator import android.testing.AndroidTestingRunner import android.view.View import androidx.test.filters.SmallTest @@ -10,13 +12,16 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition import com.android.systemui.statusbar.BlurUtils import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.anyLong import org.mockito.Mockito.eq @@ -71,6 +76,19 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { } @Test + fun testExitAnimationUpdatesState() { + controller.startExitAnimations(animatorBuilder = { mockAnimator }) + + verify(stateController).setExitAnimationsRunning(true) + + val captor = argumentCaptor<Animator.AnimatorListener>() + verify(mockAnimator).addListener(captor.capture()) + + captor.value.onAnimationEnd(mockAnimator) + verify(stateController).setExitAnimationsRunning(false) + } + + @Test fun testWakeUpCallsExecutor() { val mockExecutor: DelayableExecutor = mock() val mockCallback: Runnable = mock() @@ -87,7 +105,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { fun testWakeUpAfterStartWillCancel() { val mockStartAnimator: AnimatorSet = mock() - controller.startEntryAnimations(animatorBuilder = { mockStartAnimator }) + controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator }) verify(mockStartAnimator, never()).cancel() @@ -100,4 +118,50 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { // animator. verify(mockStartAnimator, times(1)).cancel() } + + @Test + fun testEntryAnimations_translatesUpwards() { + val mockStartAnimator: AnimatorSet = mock() + + controller.startEntryAnimations( + /* downwards= */ false, + animatorBuilder = { mockStartAnimator } + ) + + val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java) + verify(mockStartAnimator).playTogether(animatorCaptor.capture()) + + // Check if there's a ValueAnimator starting at the expected Y distance. + val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator> + assertTrue( + animators.any { + // Call setCurrentFraction so the animated value jumps to the initial value. + it.setCurrentFraction(0f) + it.animatedValue == DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat() + } + ) + } + + @Test + fun testEntryAnimations_translatesDownwards() { + val mockStartAnimator: AnimatorSet = mock() + + controller.startEntryAnimations( + /* downwards= */ true, + animatorBuilder = { mockStartAnimator } + ) + + val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java) + verify(mockStartAnimator).playTogether(animatorCaptor.capture()) + + // Check if there's a ValueAnimator starting at the expected Y distance. + val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator> + assertTrue( + animators.any { + // Call setCurrentFraction so the animated value jumps to the initial value. + it.setCurrentFraction(0f) + it.animatedValue == -DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat() + } + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index 6b095ffd3977..2a72e7d85d3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.dreams; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -33,6 +34,7 @@ import android.view.ViewTreeObserver; import androidx.test.filters.SmallTest; +import com.android.dream.lowlight.LowLightTransitionCoordinator; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.complication.ComplicationHostViewController; @@ -65,6 +67,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController; @Mock + LowLightTransitionCoordinator mLowLightTransitionCoordinator; + + @Mock DreamOverlayContainerView mDreamOverlayContainerView; @Mock @@ -109,6 +114,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mComplicationHostViewController, mDreamOverlayContentView, mDreamOverlayStatusBarViewController, + mLowLightTransitionCoordinator, mBlurUtils, mHandler, mResources, @@ -200,7 +206,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); - verify(mAnimationsController).startEntryAnimations(); + verify(mAnimationsController).startEntryAnimations(false); verify(mAnimationsController, never()).cancelAnimations(); } @@ -210,11 +216,11 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); - verify(mAnimationsController, never()).startEntryAnimations(); + verify(mAnimationsController, never()).startEntryAnimations(anyBoolean()); } @Test - public void testSkipEntryAnimationsWhenExitingLowLight() { + public void testDownwardEntryAnimationsWhenExitingLowLight() { ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor = ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); when(mStateController.isLowLightActive()).thenReturn(false); @@ -230,8 +236,14 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mController.onViewAttached(); // Entry animations should be started then immediately ended to skip to the end. - verify(mAnimationsController).startEntryAnimations(); - verify(mAnimationsController).endAnimations(); + verify(mAnimationsController).startEntryAnimations(true); + } + + @Test + public void testStartsExitAnimationsBeforeEnteringLowLight() { + mController.onBeforeEnterLowLight(); + + verify(mAnimationsController).startExitAnimations(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java index dcd8736711f6..068852de7a43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java @@ -22,6 +22,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.os.UserHandle; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.view.View; @@ -33,6 +35,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; import org.junit.Test; @@ -96,6 +100,10 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { private ComplicationHostViewController mController; + private SecureSettings mSecureSettings; + + private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -108,12 +116,17 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams); when(mComplicationView.getParent()).thenReturn(mComplicationHostView); + mSecureSettings = new FakeSettings(); + mSecureSettings.putFloatForUser( + Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, CURRENT_USER_ID); + mController = new ComplicationHostViewController( mComplicationHostView, mLayoutEngine, mDreamOverlayStateController, mLifecycleOwner, - mViewModel); + mViewModel, + mSecureSettings); mController.init(); } @@ -188,6 +201,23 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase { verify(mComplicationView, never()).setVisibility(View.INVISIBLE); } + @Test + public void testAnimationsDisabled_ComplicationsNeverSetToInvisible() { + //Disable animations + mController.mIsAnimationEnabled = false; + + final Observer<Collection<ComplicationViewModel>> observer = + captureComplicationViewModelsObserver(); + + // Add a complication before entry animations are finished. + final HashSet<ComplicationViewModel> complications = new HashSet<>( + Collections.singletonList(mComplicationViewModel)); + observer.onChanged(complications); + + // The complication view should not be set to invisible. + verify(mComplicationView, never()).setVisibility(View.INVISIBLE); + } + private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() { verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner), mObserverCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index 7f5707722b9c..e0ca90ec2c58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.media.controls.ui import android.app.PendingIntent import android.content.res.ColorStateList -import android.content.res.Configuration import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.util.MathUtils.abs @@ -685,46 +684,6 @@ class MediaCarouselControllerTest : SysuiTestCase() { } @Test - fun testOnConfigChanged_playersAreAddedBack() { - mediaCarouselController.pageIndicator = pageIndicator - - listener.value.onMediaDataLoaded( - "playing local", - null, - DATA.copy( - active = true, - isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, - resumption = false - ) - ) - listener.value.onMediaDataLoaded( - "paused local", - null, - DATA.copy( - active = true, - isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, - resumption = false - ) - ) - runAllReady() - - val playersSize = MediaPlayerData.players().size - - configListener.value.onConfigChanged(Configuration()) - runAllReady() - - verify(pageIndicator).tintList = - ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator)) - assertEquals(playersSize, MediaPlayerData.players().size) - assertEquals( - MediaPlayerData.getMediaPlayerIndex("playing local"), - mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex - ) - } - - @Test fun testOnUiModeChanged_playersAreAddedBack() { mediaCarouselController.pageIndicator = pageIndicator diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index af91cdb1522c..0fac3db2dc1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -16,9 +16,12 @@ package com.android.systemui.media.controls.ui +import android.content.res.Configuration +import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -58,6 +61,8 @@ class MediaViewControllerTest : SysuiTestCase() { @Mock private lateinit var mediaSubTitleWidgetState: WidgetState @Mock private lateinit var mediaContainerWidgetState: WidgetState @Mock private lateinit var mediaFlags: MediaFlags + @Mock private lateinit var expandedLayout: ConstraintSet + @Mock private lateinit var collapsedLayout: ConstraintSet val delta = 0.1F @@ -77,6 +82,19 @@ class MediaViewControllerTest : SysuiTestCase() { } @Test + fun testOrientationChanged_layoutsAreLoaded() { + mediaViewController.expandedLayout = expandedLayout + mediaViewController.collapsedLayout = collapsedLayout + + val newConfig = Configuration() + newConfig.orientation = ORIENTATION_LANDSCAPE + configurationController.onConfigurationChanged(newConfig) + + verify(expandedLayout).load(context, R.xml.media_session_expanded) + verify(collapsedLayout).load(context, R.xml.media_session_collapsed) + } + + @Test fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() { mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) player.measureState = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index d6dfc8509165..ac106ef9bf51 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -57,6 +57,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset @@ -351,4 +352,44 @@ class CustomTileTest : SysuiTestCase() { .startPendingIntentDismissingKeyguard( eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>()) } + + @Test + fun testActiveTileListensOnceAfterCreated() { + `when`(tileServiceManager.isActiveTile).thenReturn(true) + + val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + tile.initialize() + tile.postStale() + testableLooper.processAllMessages() + + verify(tileServiceManager).setBindRequested(true) + verify(tileService).onStartListening() + } + + @Test + fun testActiveTileDoesntListenAfterFirstTime() { + `when`(tileServiceManager.isActiveTile).thenReturn(true) + + val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + tile.initialize() + // Make sure we have an icon in the tile because we don't have a default icon + // This should not be overridden by the retrieved tile that has null icon. + tile.qsTile.icon = mock(Icon::class.java) + `when`(tile.qsTile.icon.loadDrawable(any(Context::class.java))) + .thenReturn(mock(Drawable::class.java)) + + tile.postStale() + testableLooper.processAllMessages() + + // postStale will set it to not listening after it's done + verify(tileService).onStopListening() + + clearInvocations(tileServiceManager, tileService) + + tile.setListening(Any(), true) + testableLooper.processAllMessages() + + verify(tileServiceManager, never()).setBindRequested(true) + verify(tileService, never()).onStartListening() + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt new file mode 100644 index 000000000000..eac0e296c51f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.app.Notification +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.SbnBuilder +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener +import com.android.systemui.statusbar.notification.collection.render.NotifGroupController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.time.SystemClock +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class GroupWhenCoordinatorTest : SysuiTestCase() { + + private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener + private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener + + @Mock private lateinit var pipeline: NotifPipeline + + @Mock private lateinit var delayableExecutor: DelayableExecutor + + @Mock private lateinit var groupController: NotifGroupController + + @Mock private lateinit var systemClock: SystemClock + + @InjectMocks private lateinit var coordinator: GroupWhenCoordinator + + @Before + fun setUp() { + initMocks(this) + whenever(systemClock.currentTimeMillis()).thenReturn(NOW) + coordinator.attach(pipeline) + + beforeFinalizeFilterListener = withArgCaptor { + verify(pipeline).addOnBeforeFinalizeFilterListener(capture()) + } + afterRenderGroupListener = withArgCaptor { + verify(pipeline).addOnAfterRenderGroupListener(capture()) + } + } + + @Test + fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreBeforeNow() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW - 10L) + val childEntry2 = buildNotificationEntry(2, NOW - 100L) + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2)) + .build() + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController).setNotificationGroupWhen(eq(NOW - 10L)) + } + + @Test + fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreAfterNow() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 10L) + val childEntry2 = buildNotificationEntry(2, NOW + 100L) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController).setNotificationGroupWhen(eq(NOW + 10L)) + } + + @Test + fun setNotificationGroupWhen_setClosestFutureTimeByNow_whenThereAreBothBeforeAndAfterNow() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 100L) + val childEntry2 = buildNotificationEntry(2, NOW + 10L) + val childEntry3 = buildNotificationEntry(3, NOW - 100L) + val childEntry4 = buildNotificationEntry(4, NOW - 9L) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2, childEntry3, childEntry4)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController).setNotificationGroupWhen(eq(NOW + 10L)) + } + + @Test + fun setNotificationGroupWhen_filterInvalidNotificationTimes() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 100L) + val childEntry2 = buildNotificationEntry(2, -20000L) + val childEntry3 = buildNotificationEntry(4, 0) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2, childEntry3)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController).setNotificationGroupWhen(eq(NOW + 100)) + } + + @Test + fun setNotificationGroupWhen_setSummaryTimeWhenAllNotificationTimesAreInvalid() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, 0) + val childEntry2 = buildNotificationEntry(2, -1) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(groupController, never()).setNotificationGroupWhen(NOW) + } + + @Test + fun setNotificationGroupWhen_schedulePipelineInvalidationWhenAnyNotificationIsInTheFuture() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 1000L) + val childEntry2 = buildNotificationEntry(2, NOW + 2000L) + val childEntry3 = buildNotificationEntry(3, NOW - 100L) + + val groupEntry = + GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2, childEntry3)) + .build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + // THEN + verify(delayableExecutor).executeDelayed(any(), eq(1000)) + } + + @Test + fun setNotificationGroupWhen_cancelPrevPipelineInvalidation() { + // GIVEN + val summaryEntry = buildNotificationEntry(0, NOW) + val childEntry1 = buildNotificationEntry(1, NOW + 1L) + val prevInvalidation = mock<Runnable>() + whenever(delayableExecutor.executeDelayed(any(), any())).thenReturn(prevInvalidation) + + val groupEntry = + GroupEntryBuilder().setSummary(summaryEntry).setChildren(listOf(childEntry1)).build() + + // WHEN + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + + // THEN + verify(prevInvalidation).run() + } + + private fun buildNotificationEntry(id: Int, timeMillis: Long): NotificationEntry { + val notification = Notification.Builder(mContext).setWhen(timeMillis).build() + val sbn = SbnBuilder().setNotification(notification).build() + return NotificationEntryBuilder().setId(id).setSbn(sbn).build() + } + + private companion object { + private const val NOW = 1000L + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index dd7143ae7e16..cbf841b5a1f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack; import static android.view.View.GONE; +import static android.view.WindowInsets.Type.ime; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; @@ -46,6 +47,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.Insets; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -54,6 +56,8 @@ import android.util.MathUtils; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; import android.widget.TextView; import androidx.test.annotation.UiThreadTest; @@ -91,6 +95,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.ArrayList; + /** * Tests for {@link NotificationStackScrollLayout}. */ @@ -843,6 +849,19 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { verify(mEmptyShadeView).setFooterText(not(0)); } + @Test + public void testWindowInsetAnimationProgress_updatesBottomInset() { + int bottomImeInset = 100; + mStackScrollerInternal.setAnimatedInsetsEnabled(true); + WindowInsets windowInsets = new WindowInsets.Builder() + .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build(); + ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>(); + mStackScrollerInternal + .dispatchWindowInsetsAnimationProgress(windowInsets, windowInsetsAnimations); + + assertEquals(bottomImeInset, mStackScrollerInternal.mBottomInset); + } + private void setBarStateForTest(int state) { // Can't inject this through the listener or we end up on the actual implementation // rather than the mock because the spy just coppied the anonymous inner /shruggie. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt index 1779de729e5b..7594c90daa8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt @@ -8,13 +8,14 @@ import android.view.WindowManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.TestUnfoldTransitionProvider +import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.any import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -26,6 +27,9 @@ class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var display: Display + @Mock + private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider + private val view: View = View(context) private val progressProvider = TestUnfoldTransitionProvider() private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider) @@ -36,9 +40,9 @@ class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(windowManager.defaultDisplay).thenReturn(display) - `when`(display.rotation).thenReturn(Surface.ROTATION_0) - `when`(display.getSize(any())).thenAnswer { + whenever(windowManager.defaultDisplay).thenReturn(display) + whenever(display.rotation).thenReturn(Surface.ROTATION_0) + whenever(display.getSize(any())).thenAnswer { val point = it.arguments[0] as Point point.x = 100 point.y = 100 @@ -47,7 +51,12 @@ class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() { scopedProvider.setReadyToHandleTransition(true) - controller = StatusBarMoveFromCenterAnimationController(scopedProvider, windowManager) + controller = + StatusBarMoveFromCenterAnimationController( + scopedProvider, + currentActivityTypeProvider, + windowManager + ) } @Test @@ -99,6 +108,31 @@ class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() { } @Test + fun alpha_onLauncher_alphaDoesNotChange() { + whenever(currentActivityTypeProvider.isHomeActivity).thenReturn(true) + controller.onViewsReady(arrayOf(view)) + progressProvider.onTransitionStarted() + progressProvider.onTransitionProgress(0.0f) + assertThat(view.alpha).isEqualTo(1.0f) + + progressProvider.onTransitionProgress(1.0f) + + assertThat(view.alpha).isEqualTo(1.0f) + } + + @Test + fun alpha_NotOnLauncher_alphaChanges() { + whenever(currentActivityTypeProvider.isHomeActivity).thenReturn(false) + controller.onViewsReady(arrayOf(view)) + progressProvider.onTransitionStarted() + assertThat(view.alpha).isEqualTo(1.0f) + + progressProvider.onTransitionProgress(0.5f) + + assertThat(view.alpha).isNotEqualTo(1.0f) + } + + @Test fun transitionFinished_viewReAttached_noChangesToTranslation() { controller.onViewsReady(arrayOf(view)) progressProvider.onTransitionProgress(0.5f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt index 86529dce948a..35dea60b1a1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.pipeline.mobile.shared +package com.android.systemui.statusbar.pipeline.mobile.data import android.net.Network import android.net.NetworkCapabilities diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt index 0145103d55e1..dfef62e95eda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt @@ -24,8 +24,8 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 17502f28a479..07c8cee9a3d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -27,13 +27,13 @@ import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index b2577e349da7..bd5a4d7f7385 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -53,6 +53,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -65,7 +66,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrier import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 09b7a66c925d..68b1cda62f4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -37,12 +37,12 @@ import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.TableLogBufferFactory +import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName -import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt new file mode 100644 index 000000000000..4aa48d6f25f1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt @@ -0,0 +1,100 @@ +/* + * 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.pipeline.mobile.ui + +import android.widget.TextView +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.KeyguardMobileIconViewModel +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +class MobileViewLoggerTest : SysuiTestCase() { + private val buffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10) + private val stringWriter = StringWriter() + private val printWriter = PrintWriter(stringWriter) + + private val underTest = MobileViewLogger(buffer, mock()) + + @Mock private lateinit var flags: StatusBarPipelineFlags + @Mock private lateinit var commonViewModel: MobileIconViewModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun collectionStarted_dumpHasInfo() { + val view = TextView(context) + val viewModel = QsMobileIconViewModel(commonViewModel, flags) + + underTest.logCollectionStarted(view, viewModel) + + val dumpString = getDumpString() + assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true") + } + + @Test + fun collectionStarted_multipleViews_dumpHasInfo() { + val view = TextView(context) + val view2 = TextView(context) + val viewModel = QsMobileIconViewModel(commonViewModel, flags) + val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags) + + underTest.logCollectionStarted(view, viewModel) + underTest.logCollectionStarted(view2, viewModel2) + + val dumpString = getDumpString() + assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true") + assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true") + } + + @Test + fun collectionStopped_dumpHasInfo() { + val view = TextView(context) + val view2 = TextView(context) + val viewModel = QsMobileIconViewModel(commonViewModel, flags) + val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags) + + underTest.logCollectionStarted(view, viewModel) + underTest.logCollectionStarted(view2, viewModel2) + underTest.logCollectionStopped(view, viewModel) + + val dumpString = getDumpString() + assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=false") + assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true") + } + + private fun getDumpString(): String { + underTest.dump(printWriter, args = arrayOf()) + return stringWriter.toString() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt index e68a3970ae93..7420db2e895e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel @@ -60,6 +61,7 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var tableLogBuffer: TableLogBuffer + @Mock private lateinit var viewLogger: MobileViewLogger @Mock private lateinit var constants: ConnectivityConstants private lateinit var interactor: FakeMobileIconInteractor private lateinit var airplaneModeRepository: FakeAirplaneModeRepository @@ -94,7 +96,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun setVisibleState_icon_iconShownDotHidden() { - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false) @@ -109,8 +117,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun setVisibleState_dot_iconHiddenDotShown() { - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false) ViewUtils.attachView(view) @@ -124,8 +137,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun setVisibleState_hidden_iconAndDotHidden() { - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false) ViewUtils.attachView(view) @@ -142,8 +160,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { whenever(constants.hasDataCapabilities).thenReturn(false) createViewModel() - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -157,8 +180,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { whenever(constants.hasDataCapabilities).thenReturn(true) createViewModel() - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -171,8 +199,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { fun isIconVisible_notAirplaneMode_outputsTrue() { airplaneModeRepository.setIsAirplaneMode(false) - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -185,8 +218,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { fun isIconVisible_airplaneMode_outputsTrue() { airplaneModeRepository.setIsAirplaneMode(true) - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) - + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -198,7 +236,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun onDarkChanged_iconHasNewColor() { whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() @@ -214,7 +258,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() { @Test fun setStaticDrawableColor_iconHasNewColor() { whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false) - val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel) + val view = + ModernStatusBarMobileView.constructAndBind( + context, + viewLogger, + SLOT_NAME, + viewModel, + ) ViewUtils.attachView(view) testableLooper.processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index f9830309252d..a6d915243f60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn @@ -84,7 +85,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { testScope.backgroundScope, ) - homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags) + homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags, mock()) qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags) keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index 4628f8410245..ddb7f4d88d30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -24,6 +24,8 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirp import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger +import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository @@ -51,6 +53,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { private lateinit var airplaneModeInteractor: AirplaneModeInteractor @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var constants: ConnectivityConstants + @Mock private lateinit var logger: MobileViewLogger + @Mock private lateinit var verboseLogger: VerboseMobileViewLogger private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -73,6 +77,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { underTest = MobileIconsViewModel( subscriptionIdsFlow, + logger, + verboseLogger, interactor, airplaneModeInteractor, constants, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt index e4c8fd0cd8a1..b4039d906810 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt @@ -164,6 +164,10 @@ class ModernStatusBarViewTest : SysuiTestCase() { override fun getShouldIconBeVisible(): Boolean { return shouldIconBeVisibleInternal } + + override fun isCollecting(): Boolean { + return true + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index 48b17322da4d..481d453fa0b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -38,6 +38,7 @@ import com.android.internal.R; import com.android.internal.view.RotationPolicy; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wrapper.RotationPolicyWrapper; @@ -55,10 +56,12 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"}; + @Mock private DeviceStateManager mDeviceStateManager; + @Mock private DeviceStateRotationLockSettingControllerLogger mLogger; + @Mock private DumpManager mDumpManager; + private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); - @Mock - private DeviceStateManager mDeviceStateManager; private final RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy(); private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; @@ -78,7 +81,13 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext); mDeviceStateRotationLockSettingController = new DeviceStateRotationLockSettingController( - mFakeRotationPolicy, mDeviceStateManager, mFakeExecutor, mSettingsManager); + mFakeRotationPolicy, + mDeviceStateManager, + mFakeExecutor, + mSettingsManager, + mLogger, + mDumpManager + ); mDeviceStateRotationLockSettingController.setListening(true); verify(mDeviceStateManager) @@ -173,15 +182,11 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Test - public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() { - initializeSettingsWith( - 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); - mFakeRotationPolicy.setRotationLock(true); - - mDeviceStateCallback.onStateChanged(1); - assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - + public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() { mDeviceStateCallback.onStateChanged(0); + assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); + + mDeviceStateCallback.onStateChanged(2); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt index a07966823c81..c3a6cf035d09 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt @@ -27,6 +27,7 @@ import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver import com.android.systemui.unfold.updates.FoldProvider import com.android.systemui.unfold.updates.RotationChangeProvider +import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.util.CurrentActivityTypeProvider import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix @@ -67,6 +68,7 @@ interface UnfoldSharedComponent { } val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider> + val hingeAngleProvider: HingeAngleProvider val rotationChangeProvider: RotationChangeProvider } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 0ee883f745ca..f236a961fcb6 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -402,6 +402,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void systemServicesReady() { mStats.systemServicesReady(mContext); + mCpuWakeupStats.systemServicesReady(); mWorker.systemServicesReady(); final INetworkManagementService nms = INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index bb8d3f4c2d7d..23a384ff5d3b 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -67,8 +67,8 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private SparseIntArray mPendingUidStates = new SparseIntArray(); private SparseIntArray mCapability = new SparseIntArray(); private SparseIntArray mPendingCapability = new SparseIntArray(); - private SparseBooleanArray mVisibleAppWidget = new SparseBooleanArray(); - private SparseBooleanArray mPendingVisibleAppWidget = new SparseBooleanArray(); + private SparseBooleanArray mAppWidgetVisible = new SparseBooleanArray(); + private SparseBooleanArray mPendingAppWidgetVisible = new SparseBooleanArray(); private SparseLongArray mPendingCommitTime = new SparseLongArray(); private SparseBooleanArray mPendingGone = new SparseBooleanArray(); @@ -140,7 +140,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private int evalModeInternal(int uid, int code, int uidState, int uidCapability) { - if (getUidVisibleAppWidget(uid) || mActivityManagerInternal.isPendingTopUid(uid) + if (getUidAppWidgetVisible(uid) || mActivityManagerInternal.isPendingTopUid(uid) || mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) { return MODE_ALLOWED; } @@ -205,7 +205,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { int numUids = uidPackageNames.size(); for (int i = 0; i < numUids; i++) { int uid = uidPackageNames.keyAt(i); - mPendingVisibleAppWidget.put(uid, visible); + mPendingAppWidgetVisible.put(uid, visible); commitUidPendingState(uid); } @@ -291,9 +291,9 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { ActivityManager.printCapabilitiesFull(pw, pendingCapability); pw.println(); } - boolean appWidgetVisible = mVisibleAppWidget.get(uid, false); + boolean appWidgetVisible = mAppWidgetVisible.get(uid, false); // if no pendingAppWidgetVisible set to appWidgetVisible to suppress output - boolean pendingAppWidgetVisible = mPendingVisibleAppWidget.get(uid, appWidgetVisible); + boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid, appWidgetVisible); pw.print(" appWidgetVisible="); pw.println(appWidgetVisible); if (appWidgetVisible != pendingAppWidgetVisible) { @@ -333,25 +333,25 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { mUidStates.get(uid, MIN_PRIORITY_UID_STATE)); int pendingCapability = mPendingCapability.get(uid, mCapability.get(uid, PROCESS_CAPABILITY_NONE)); - boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid, - mVisibleAppWidget.get(uid, false)); + boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid, + mAppWidgetVisible.get(uid, false)); int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE); int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); - boolean visibleAppWidget = mVisibleAppWidget.get(uid, false); + boolean appWidgetVisible = mAppWidgetVisible.get(uid, false); if (uidState != pendingUidState || capability != pendingCapability - || visibleAppWidget != pendingVisibleAppWidget) { + || appWidgetVisible != pendingAppWidgetVisible) { boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED || capability != pendingCapability - || visibleAppWidget != pendingVisibleAppWidget; + || appWidgetVisible != pendingAppWidgetVisible; if (foregroundChange) { // To save on memory usage, log only interesting changes. mEventLog.logCommitUidState(uid, pendingUidState, pendingCapability, - pendingVisibleAppWidget); + pendingAppWidgetVisible, appWidgetVisible != pendingAppWidgetVisible); } for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) { @@ -367,17 +367,17 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { if (mPendingGone.get(uid, false)) { mUidStates.delete(uid); mCapability.delete(uid); - mVisibleAppWidget.delete(uid); + mAppWidgetVisible.delete(uid); mPendingGone.delete(uid); } else { mUidStates.put(uid, pendingUidState); mCapability.put(uid, pendingCapability); - mVisibleAppWidget.put(uid, pendingVisibleAppWidget); + mAppWidgetVisible.put(uid, pendingAppWidgetVisible); } mPendingUidStates.delete(uid); mPendingCapability.delete(uid); - mPendingVisibleAppWidget.delete(uid); + mPendingAppWidgetVisible.delete(uid); mPendingCommitTime.delete(uid); } @@ -385,8 +385,8 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { return mCapability.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE); } - private boolean getUidVisibleAppWidget(int uid) { - return mVisibleAppWidget.get(uid, false); + private boolean getUidAppWidgetVisible(int uid) { + return mAppWidgetVisible.get(uid, false); } private static class EventLog { @@ -398,6 +398,9 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { // Memory usage: 24 * size bytes private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200; + private static final int APP_WIDGET_VISIBLE = 1 << 0; + private static final int APP_WIDGET_VISIBLE_CHANGED = 1 << 1; + private final DelayableExecutor mExecutor; private final Thread mExecutorThread; @@ -446,16 +449,18 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { mUpdateUidProcStateLogTimestamps[idx] = timestamp; } - void logCommitUidState(int uid, int uidState, int capability, boolean visible) { + void logCommitUidState(int uid, int uidState, int capability, boolean appWidgetVisible, + boolean appWidgetVisibleChanged) { if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) { return; } mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync, - this, System.currentTimeMillis(), uid, uidState, capability, visible)); + this, System.currentTimeMillis(), uid, uidState, capability, appWidgetVisible, + appWidgetVisibleChanged)); } void logCommitUidStateAsync(long timestamp, int uid, int uidState, int capability, - boolean visible) { + boolean appWidgetVisible, boolean appWidgetVisibleChanged) { int idx = (mCommitUidStateLogHead + mCommitUidStateLogSize) % COMMIT_UID_STATE_LOG_MAX_SIZE; if (mCommitUidStateLogSize == COMMIT_UID_STATE_LOG_MAX_SIZE) { @@ -468,7 +473,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { mCommitUidStateLog[idx][0] = uid; mCommitUidStateLog[idx][1] = uidState; mCommitUidStateLog[idx][2] = capability; - mCommitUidStateLog[idx][3] = visible ? 1 : 0; + mCommitUidStateLog[idx][3] = 0; + if (appWidgetVisible) { + mCommitUidStateLog[idx][3] += APP_WIDGET_VISIBLE; + } + if (appWidgetVisibleChanged) { + mCommitUidStateLog[idx][3] += APP_WIDGET_VISIBLE_CHANGED; + } mCommitUidStateLogTimestamps[idx] = timestamp; } @@ -570,7 +581,9 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { int uid = mCommitUidStateLog[idx][0]; int uidState = mCommitUidStateLog[idx][1]; int capability = mCommitUidStateLog[idx][2]; - boolean visibleAppWidget = mCommitUidStateLog[idx][3] != 0; + boolean appWidgetVisible = (mCommitUidStateLog[idx][3] & APP_WIDGET_VISIBLE) != 0; + boolean appWidgetVisibleChanged = + (mCommitUidStateLog[idx][3] & APP_WIDGET_VISIBLE_CHANGED) != 0; TimeUtils.dumpTime(pw, timestamp); @@ -585,8 +598,12 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { pw.print(" capability="); pw.print(ActivityManager.getCapabilitiesSummary(capability) + " "); - pw.print(" visibleAppWidget="); - pw.print(visibleAppWidget); + pw.print(" appWidgetVisible="); + pw.print(appWidgetVisible); + + if (appWidgetVisibleChanged) { + pw.print(" (changed)"); + } pw.println(); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 0d0e5764b522..c50e275ddfa0 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -8588,7 +8588,9 @@ public class AudioService extends IAudioService.Stub if (isMutable()) { // For call stream, align mute only when muted, not when index is set to 0 mVolumeGroupState.mute( - forceMuteState ? mIsMuted : groupIndex == 0 || mIsMuted); + forceMuteState ? mIsMuted : + (groupIndex == 0 && !isCallStream(mStreamType)) + || mIsMuted); } } } diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java index ab84ae4c08a2..df70a32c232c 100644 --- a/services/core/java/com/android/server/dreams/DreamShellCommand.java +++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java @@ -39,26 +39,24 @@ public class DreamShellCommand extends ShellCommand { @Override public int onCommand(String cmd) { - final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.ROOT_UID) { - Slog.e(TAG, "Must be root before calling Dream shell commands"); - return -1; - } - - if (TextUtils.isEmpty(cmd)) { - return super.handleDefaultCommands(cmd); - } if (DEBUG) { Slog.d(TAG, "onCommand:" + cmd); } - switch (cmd) { - case "start-dreaming": - return startDreaming(); - case "stop-dreaming": - return stopDreaming(); - default: - return super.handleDefaultCommands(cmd); + try { + switch (cmd) { + case "start-dreaming": + enforceCallerIsRoot(); + return startDreaming(); + case "stop-dreaming": + enforceCallerIsRoot(); + return stopDreaming(); + default: + return super.handleDefaultCommands(cmd); + } + } catch (SecurityException e) { + getOutPrintWriter().println(e); + return -1; } } @@ -72,6 +70,12 @@ public class DreamShellCommand extends ShellCommand { return 0; } + private void enforceCallerIsRoot() { + if (Binder.getCallingUid() != Process.ROOT_UID) { + throw new SecurityException("Must be root to call Dream shell commands"); + } + } + @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java index 706aedcbba7b..b05b662dc1e8 100644 --- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java +++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java @@ -22,7 +22,9 @@ import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; import android.content.Context; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.LongSparseArray; @@ -40,6 +42,8 @@ import com.android.internal.util.IntPair; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -52,13 +56,13 @@ public class CpuWakeupStats { private static final String SUBSYSTEM_ALARM_STRING = "Alarm"; private static final String SUBSYSTEM_ALARM_WIFI = "Wifi"; @VisibleForTesting - static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days. - @VisibleForTesting static final long WAKEUP_REASON_HALF_WINDOW_MS = 500; - private static final long WAKEUP_WRITE_DELAY_MS = 2 * 60 * 1000; // 2 minutes. + private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.MINUTES.toMillis(2); private final Handler mHandler; private final IrqDeviceMap mIrqDeviceMap; + @VisibleForTesting + final Config mConfig = new Config(); private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory(); @VisibleForTesting @@ -72,6 +76,14 @@ public class CpuWakeupStats { mHandler = handler; } + /** + * Called on the boot phase SYSTEM_SERVICES_READY. + * This ensures that DeviceConfig is ready for calls to read properties. + */ + public synchronized void systemServicesReady() { + mConfig.register(new HandlerExecutor(mHandler)); + } + private static int subsystemToStatsReason(int subsystem) { switch (subsystem) { case CPU_WAKEUP_SUBSYSTEM_ALARM: @@ -136,14 +148,15 @@ public class CpuWakeupStats { // we can delete all history that will not be useful in attributing future wakeups. mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS); - // Limit history of wakeups and their attribution to the last WAKEUP_RETENTION_MS. Note that + // Limit history of wakeups and their attribution to the last retentionDuration. Note that // the last wakeup and its attribution (if computed) is always stored, even if that wakeup - // had occurred before WAKEUP_RETENTION_MS. - int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS); + // had occurred before retentionDuration. + final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS; + int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - retentionDuration); for (int i = lastIdx; i >= 0; i--) { mWakeupEvents.removeAt(i); } - lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS); + lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - retentionDuration); for (int i = lastIdx; i >= 0; i--) { mWakeupAttribution.removeAt(i); } @@ -226,6 +239,9 @@ public class CpuWakeupStats { pw.println("CPU wakeup stats:"); pw.increaseIndent(); + mConfig.dump(pw); + pw.println(); + mIrqDeviceMap.dump(pw); pw.println(); @@ -296,7 +312,8 @@ public class CpuWakeupStats { } private static final class WakingActivityHistory { - private static final long WAKING_ACTIVITY_RETENTION_MS = 3 * 60 * 60_000; // 3 hours. + private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10); + private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity = new SparseArray<>(); @@ -521,4 +538,52 @@ public class CpuWakeupStats { } } } + + static final class Config implements DeviceConfig.OnPropertiesChangedListener { + static final String KEY_WAKEUP_STATS_RETENTION_MS = "wakeup_stats_retention_ms"; + + private static final String[] PROPERTY_NAMES = { + KEY_WAKEUP_STATS_RETENTION_MS, + }; + + static final long DEFAULT_WAKEUP_STATS_RETENTION_MS = TimeUnit.DAYS.toMillis(3); + + /** + * Wakeup stats are retained only for this duration. + */ + public volatile long WAKEUP_STATS_RETENTION_MS = DEFAULT_WAKEUP_STATS_RETENTION_MS; + + void register(Executor executor) { + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS, + executor, this); + onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BATTERY_STATS, + PROPERTY_NAMES)); + } + + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + for (String name : properties.getKeyset()) { + if (name == null) { + continue; + } + switch (name) { + case KEY_WAKEUP_STATS_RETENTION_MS: + WAKEUP_STATS_RETENTION_MS = properties.getLong( + KEY_WAKEUP_STATS_RETENTION_MS, DEFAULT_WAKEUP_STATS_RETENTION_MS); + break; + } + } + } + + void dump(IndentingPrintWriter pw) { + pw.println("Config:"); + + pw.increaseIndent(); + + pw.print(KEY_WAKEUP_STATS_RETENTION_MS, WAKEUP_STATS_RETENTION_MS); + pw.println(); + + pw.decreaseIndent(); + } + } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 5bace0ebe13a..88d64df99d48 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -151,6 +151,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2) static final long REQUEST_LISTENING_MUST_MATCH_PACKAGE = 172251878L; + /** + * @hide + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + static final long REQUEST_LISTENING_OTHER_USER_NOOP = 242194868L; + private final Context mContext; private final Handler mHandler = new Handler(); @@ -1888,7 +1895,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D // Check current user if (userId != currentUser) { - throw new IllegalArgumentException("User " + userId + " is not the current user."); + if (CompatChanges.isChangeEnabled(REQUEST_LISTENING_OTHER_USER_NOOP, callingUid)) { + return; + } else { + throw new IllegalArgumentException( + "User " + userId + " is not the current user."); + } } } if (mBar != null) { diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 404ca017929d..c6684dfdb6e0 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2125,6 +2125,26 @@ public final class TvInputManagerService extends SystemService { } @Override + public void notifyTvMessage(IBinder sessionToken, String type, Bundle data, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftEnablePositionTracking"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .notifyTvMessage(type, data); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftEnablePositionTracking", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void startRecording(IBinder sessionToken, @Nullable Uri programUri, @Nullable Bundle params, int userId) { final int callingUid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 37e4890bad0e..41fee1c5187d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8145,7 +8145,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Clear config override in #updateCompatDisplayInsets(). - onRequestedOverrideConfigurationChanged(EMPTY); + final int activityType = getActivityType(); + final Configuration overrideConfig = getRequestedOverrideConfiguration(); + overrideConfig.unset(); + // Keep the activity type which was set when attaching to a task to prevent leaving it + // undefined. + overrideConfig.windowConfiguration.setActivityType(activityType); + onRequestedOverrideConfigurationChanged(overrideConfig); } @Override diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index 49db5f710004..e84f0cc17d05 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -31,6 +31,7 @@ import android.util.Log; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -120,22 +121,22 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta Log.i(TAG, "respondToClientWithResponseAndFinish"); if (isSessionCancelled()) { mChosenProviderMetric.setChosenProviderStatus( - MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS); + ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } try { mClientCallback.onSuccess(); logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_SUCCESS); + ApiStatus.SUCCESS); } catch (RemoteException e) { mChosenProviderMetric.setChosenProviderStatus( - MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE); + ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); Log.i(TAG, "Issue while propagating the response to the client"); logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_FAILURE); + ApiStatus.FAILURE); } finishSession(/*propagateCancellation=*/false); } @@ -144,7 +145,7 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta Log.i(TAG, "respondToClientWithErrorAndFinish"); if (isSessionCancelled()) { logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } @@ -154,7 +155,7 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta e.printStackTrace(); } logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_FAILURE); + ApiStatus.FAILURE); finishSession(/*propagateCancellation=*/false); } diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 90e10679abcd..7e1780d05d75 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -16,9 +16,6 @@ package com.android.server.credentials; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -38,6 +35,7 @@ import android.util.Log; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -103,11 +101,11 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR setChosenMetric(componentName); if (response != null) { mChosenProviderMetric.setChosenProviderStatus( - METRICS_PROVIDER_STATUS_FINAL_SUCCESS); + ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); respondToClientWithResponseAndFinish(response); } else { mChosenProviderMetric.setChosenProviderStatus( - METRICS_PROVIDER_STATUS_FINAL_FAILURE); + ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, "Invalid response"); } @@ -144,18 +142,18 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR } if (isSessionCancelled()) { logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } try { mClientCallback.onResponse(response); logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_SUCCESS); + ApiStatus.SUCCESS); } catch (RemoteException e) { Log.i(TAG, "Issue while responding to client: " + e.getMessage()); logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_FAILURE); + ApiStatus.FAILURE); } finishSession(/*propagateCancellation=*/false); } @@ -168,7 +166,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR } if (isSessionCancelled()) { logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } @@ -184,10 +182,10 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR private void logFailureOrUserCancel(String errorType) { if (CreateCredentialException.TYPE_USER_CANCELED.equals(errorType)) { logApiCall(ApiName.CREATE_CREDENTIAL, - /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED); + /* apiStatus */ ApiStatus.USER_CANCELED); } else { logApiCall(ApiName.CREATE_CREDENTIAL, - /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE); + /* apiStatus */ ApiStatus.FAILURE); } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 8170dc30d09d..9c870050449c 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -631,7 +631,7 @@ public final class CredentialManagerService // The component name and the package name do not match. MetricUtilities.logApiCalled( ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, - ApiStatus.METRICS_API_STATUS_FAILURE, callingUid); + ApiStatus.FAILURE, callingUid); Log.w( TAG, "isEnabledCredentialProviderService: Component name does not" @@ -639,7 +639,7 @@ public final class CredentialManagerService return false; } MetricUtilities.logApiCalled(ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, - ApiStatus.METRICS_API_STATUS_SUCCESS, callingUid); + ApiStatus.SUCCESS, callingUid); return true; } } diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index 4af2713a5cf8..8c6e5cea3bba 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -16,9 +16,6 @@ package com.android.server.credentials; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS; - import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -36,6 +33,7 @@ import android.util.Log; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -95,11 +93,11 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest setChosenMetric(componentName); if (response != null) { mChosenProviderMetric.setChosenProviderStatus( - METRICS_PROVIDER_STATUS_FINAL_SUCCESS); + ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); respondToClientWithResponseAndFinish(response); } else { mChosenProviderMetric.setChosenProviderStatus( - METRICS_PROVIDER_STATUS_FINAL_FAILURE); + ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, "Invalid response from provider"); } @@ -120,18 +118,18 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest } if (isSessionCancelled()) { logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } try { mClientCallback.onResponse(response); logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_SUCCESS); + ApiStatus.SUCCESS); } catch (RemoteException e) { Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage()); logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_FAILURE); + ApiStatus.FAILURE); } finishSession(/*propagateCancellation=*/false); } @@ -143,7 +141,7 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest } if (isSessionCancelled()) { logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */ - ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED); + ApiStatus.CLIENT_CANCELED); finishSession(/*propagateCancellation=*/true); return; } @@ -160,10 +158,10 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest private void logFailureOrUserCancel(String errorType) { if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) { logApiCall(ApiName.GET_CREDENTIAL, - /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED); + /* apiStatus */ ApiStatus.USER_CANCELED); } else { logApiCall(ApiName.GET_CREDENTIAL, - /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE); + /* apiStatus */ ApiStatus.FAILURE); } } diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index e7b0a2d9f731..880ae6d4201e 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -16,12 +16,6 @@ package com.android.server.credentials; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS; -import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN; - import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -46,18 +40,6 @@ public class MetricUtilities { public static final int DEFAULT_INT_32 = -1; public static final int[] DEFAULT_REPEATED_INT_32 = new int[0]; - // Metrics constants TODO(b/269290341) migrate to enums eventually to improve - protected static final int METRICS_PROVIDER_STATUS_FINAL_FAILURE = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE; - protected static final int METRICS_PROVIDER_STATUS_QUERY_FAILURE = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE; - protected static final int METRICS_PROVIDER_STATUS_FINAL_SUCCESS = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS; - protected static final int METRICS_PROVIDER_STATUS_QUERY_SUCCESS = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS; - protected static final int METRICS_PROVIDER_STATUS_UNKNOWN = - CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN; - /** * This retrieves the uid of any package name, given a context and a component name for the diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index fc179e4526f5..a85769572972 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -16,9 +16,6 @@ package com.android.server.credentials; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_FAILURE; -import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_SUCCESS; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,6 +32,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.server.credentials.metrics.CandidateProviderMetric; +import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.UUID; @@ -205,9 +203,11 @@ public abstract class ProviderSession<T, R> mCandidateProviderMetric .setQueryFinishTimeNanoseconds(System.nanoTime()); if (isTerminatingStatus(status)) { - mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_FAILURE); + mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_FAILURE + .getMetricCode()); } else if (isCompletionStatus(status)) { - mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_SUCCESS); + mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_SUCCESS + .getMetricCode()); } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java index 36a1f2df6d24..22cab707387d 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java @@ -22,11 +22,11 @@ import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED; public enum ApiStatus { - METRICS_API_STATUS_SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS), - METRICS_API_STATUS_FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE), - METRICS_API_STATUS_CLIENT_CANCELED( + SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS), + FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE), + CLIENT_CANCELED( CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_CLIENT_CANCELED), - METRICS_API_STATUS_USER_CANCELED( + USER_CANCELED( CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED); private final int mInnerMetricCode; diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java index f49995d041aa..9f438ecc1146 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java @@ -18,6 +18,9 @@ package com.android.server.credentials.metrics; /** * The central candidate provider metric object that mimics our defined metric setup. + * Some types are redundant across these metric collectors, but that has debug use-cases as + * these data-types are available at different moments of the flow (and typically, one can feed + * into the next). * TODO(b/270403549) - iterate on this in V3+ */ public class CandidateProviderMetric { diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java index 75fdc567013c..03102558d21b 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java @@ -22,6 +22,9 @@ import com.android.server.credentials.MetricUtilities; /** * The central chosen provider metric object that mimics our defined metric setup. + * Some types are redundant across these metric collectors, but that has debug use-cases as + * these data-types are available at different moments of the flow (and typically, one can feed + * into the next). * TODO(b/270403549) - iterate on this in V3+ */ public class ChosenProviderMetric { @@ -65,12 +68,12 @@ public class ChosenProviderMetric { /** * In order for a chosen provider to be selected, the call must have successfully begun. - * Thus, the {@link PreCandidateMetric} can directly pass this initial latency figure into + * Thus, the {@link InitialPhaseMetric} can directly pass this initial latency figure into * this chosen provider metric. * * @param preQueryPhaseLatencyMicroseconds the millisecond latency for the service start, * typically passed in through the - * {@link PreCandidateMetric} + * {@link InitialPhaseMetric} */ public void setPreQueryPhaseLatencyMicroseconds(int preQueryPhaseLatencyMicroseconds) { mPreQueryPhaseLatencyMicroseconds = preQueryPhaseLatencyMicroseconds; @@ -112,7 +115,7 @@ public class ChosenProviderMetric { /** * Returns the full (platform invoked to response) latency in microseconds. Expects the - * start time to be provided, such as from {@link PreCandidateMetric}. + * start time to be provided, such as from {@link InitialPhaseMetric}. */ public int getEntireLatencyMicroseconds() { return (int) ((this.mFinalFinishTimeNanoseconds @@ -123,11 +126,11 @@ public class ChosenProviderMetric { /** * In order for a chosen provider to be selected, the call must have successfully begun. - * Thus, the {@link PreCandidateMetric} can directly pass this initial timestamp into this + * Thus, the {@link InitialPhaseMetric} can directly pass this initial timestamp into this * chosen provider metric. * * @param serviceBeganTimeNanoseconds the timestamp moment when the platform was called, - * typically passed in through the {@link PreCandidateMetric} + * typically passed in through the {@link InitialPhaseMetric} */ public void setServiceBeganTimeNanoseconds(long serviceBeganTimeNanoseconds) { mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds; @@ -188,8 +191,6 @@ public class ChosenProviderMetric { - this.mServiceBeganTimeNanoseconds) / 1000); } - - /* ----------- Provider Status -------------- */ public int getChosenProviderStatus() { diff --git a/services/credentials/java/com/android/server/credentials/metrics/PreCandidateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java index 952328f6ba3a..5f062b05df67 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/PreCandidateMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java @@ -18,19 +18,34 @@ package com.android.server.credentials.metrics; /** * This handles metrics collected prior to any remote calls to providers. + * Some types are redundant across these metric collectors, but that has debug use-cases as + * these data-types are available at different moments of the flow (and typically, one can feed + * into the next). * TODO(b/270403549) - iterate on this in V3+ */ -public class PreCandidateMetric { - +public class InitialPhaseMetric { private static final String TAG = "PreCandidateMetric"; + // The api being called, default set to unknown + private int mApiName = ApiName.UNKNOWN.getMetricCode(); + // The caller uid of the calling application, default to -1 + private int mCallerUid = -1; + // The session id to unite multiple atom emits, default to -1 + private long mSessionId = -1; + // A sequence id to order united emits, default to -1 + private int mSequenceId = -1; + private int mCountRequestClassType = -1; + // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a // reference point. - private long mCredentialServiceStartedTimeNanoseconds = -1; + + // A reference point to give this object utility to capture latency. Can be directly handed + // over to the next latency object. private long mCredentialServiceBeginQueryTimeNanoseconds = -1; - public PreCandidateMetric() { + + public InitialPhaseMetric() { } /* ---------- Latencies ---------- */ @@ -62,4 +77,54 @@ public class PreCandidateMetric { public long getCredentialServiceBeginQueryTimeNanoseconds() { return mCredentialServiceBeginQueryTimeNanoseconds; } + + /* ------ ApiName ------ */ + + public void setApiName(int apiName) { + mApiName = apiName; + } + + public int getApiName() { + return mApiName; + } + + /* ------ CallerUid ------ */ + + public void setCallerUid(int callerUid) { + mCallerUid = callerUid; + } + + public int getCallerUid() { + return mCallerUid; + } + + /* ------ SessionId ------ */ + + public void setSessionId(long sessionId) { + mSessionId = sessionId; + } + + public long getSessionId() { + return mSessionId; + } + + /* ------ SequenceId ------ */ + + public void setSequenceId(int sequenceId) { + mSequenceId = sequenceId; + } + + public int getSequenceId() { + return mSequenceId; + } + + /* ------ Count Request Class Types ------ */ + + public void setCountRequestClassType(int countRequestClassType) { + mCountRequestClassType = countRequestClassType; + } + + public int getCountRequestClassType() { + return mCountRequestClassType; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java new file mode 100644 index 000000000000..08f1afa2f438 --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java @@ -0,0 +1,52 @@ +/* + * 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.credentials.metrics; + +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN; + +public enum ProviderStatusForMetrics { + + UNKNOWN( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN), + FINAL_FAILURE( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE), + QUERY_FAILURE( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE), + FINAL_SUCCESS( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS), + QUERY_SUCCESS( + CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS); + + private final int mInnerMetricCode; + + ProviderStatusForMetrics(int innerMetricCode) { + this.mInnerMetricCode = innerMetricCode; + } + + /** + * Gives the West-world version of the metric name. + * + * @return a code corresponding to the west world metric name + */ + public int getMetricCode() { + return this.mInnerMetricCode; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java index cd5ac7bcb1e5..1731590be3c9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java @@ -782,6 +782,34 @@ public class AppOpsUidStateTrackerTest { assertTrue(mIntf.isUidInForeground(UID)); } + @Test + public void testAppWidgetVisibleDoesntChangeUidState() { + procStateBuilder(UID) + .topState() + .update(); + + SparseArray<String> updatedAppWidgetVisibilities = new SparseArray<>(); + updatedAppWidgetVisibilities.put(UID, ""); + + mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, true); + + assertEquals(UID_STATE_TOP, mIntf.getUidState(UID)); + } + + @Test + public void testAppWidgetNotVisibleDoesntChangeUidState() { + SparseArray<String> updatedAppWidgetVisibilities = new SparseArray<>(); + updatedAppWidgetVisibilities.put(UID, ""); + mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, true); + procStateBuilder(UID) + .topState() + .update(); + + mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, false); + + assertEquals(UID_STATE_TOP, mIntf.getUidState(UID)); + } + public void testUidStateChangedCallback(int initialState, int finalState) { int initialUidState = processStateToUidState(initialState); int finalUidState = processStateToUidState(finalState); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java index 178670e678f0..397d7b5f2a3e 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java @@ -21,7 +21,6 @@ import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS; -import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS; import static com.google.common.truth.Truth.assertThat; @@ -66,6 +65,7 @@ public class CpuWakeupStatsTest { public void removesOldWakeups() { // The xml resource doesn't matter for this test. final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler); + final long retention = obj.mConfig.WAKEUP_STATS_RETENTION_MS; final Set<Long> timestamps = new HashSet<>(); final long firstWakeup = 453192; @@ -73,22 +73,21 @@ public class CpuWakeupStatsTest { obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_UNKNOWN_IRQ); timestamps.add(firstWakeup); for (int i = 1; i < 1000; i++) { - final long delta = mRandom.nextLong(WAKEUP_RETENTION_MS); + final long delta = mRandom.nextLong(retention); if (timestamps.add(firstWakeup + delta)) { obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_UNKNOWN_IRQ); } } assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size()); - obj.noteWakeupTimeAndReason(firstWakeup + WAKEUP_RETENTION_MS + 1242, 231, + obj.noteWakeupTimeAndReason(firstWakeup + retention + 1242, 231, KERNEL_REASON_UNKNOWN_IRQ); assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size()); for (int i = 0; i < 100; i++) { - final long now = mRandom.nextLong(WAKEUP_RETENTION_MS + 1, 100 * WAKEUP_RETENTION_MS); + final long now = mRandom.nextLong(retention + 1, 100 * retention); obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_UNKNOWN_IRQ); - assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - WAKEUP_RETENTION_MS)) - .isLessThan(0); + assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0); } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt index 19ecf6ab8799..05abf9fd1a8e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt @@ -53,7 +53,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { +open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt new file mode 100644 index 000000000000..46899f373fcf --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt @@ -0,0 +1,51 @@ +/* + * 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.wm.flicker.launch + +import android.tools.common.NavBar +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** Some assertions will fail because of b/264415996 */ +@FlickerServiceCompatible +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + // TAPL fails on landscape mode b/240916028 + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_3BUTTON) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt index da985232a1e3..b848e63c9c87 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt @@ -93,8 +93,7 @@ class OverrideTaskTransitionTest(val flicker: FlickerTest) { .then() // Animation starts, but the app may not be drawn yet which means the Splash // may be visible. - .isInvisible(testApp, isOptional = true) - .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) + .isSplashScreenVisibleFor(testApp, isOptional = true) .then() // App shows up with the custom animation starting at alpha=1. .isVisible(testApp) |