diff options
223 files changed, 4064 insertions, 1693 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING index 3409838b5611..117faa203325 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -126,6 +126,12 @@ "exclude-annotation": "org.junit.Ignore" } ] + }, + { + "name": "vts_treble_vintf_framework_test" + }, + { + "name": "vts_treble_vintf_vendor_test" } ], "postsubmit-ravenwood": [ diff --git a/api/Android.bp b/api/Android.bp index 2b1cfcb82d04..9029d252b470 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -31,12 +31,10 @@ 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 71b1e10d2f47..2668999c572e 100644 --- a/api/api.go +++ b/api/api.go @@ -22,7 +22,6 @@ import ( "github.com/google/blueprint/proptools" "android/soong/android" - "android/soong/bazel" "android/soong/genrule" "android/soong/java" ) @@ -65,7 +64,6 @@ type CombinedApisProperties struct { type CombinedApis struct { android.ModuleBase - android.BazelModuleBase properties CombinedApisProperties } @@ -115,20 +113,6 @@ type defaultsProps struct { Previous_api *string } -type Bazel_module struct { - Label *string - 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" @@ -143,8 +127,6 @@ type MergedTxtDefinition struct { ModuleTag string // public, system, module-lib or system-server Scope string - // True if there is a bp2build definition for this module - Bp2buildDefined bool } func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) { @@ -178,20 +160,7 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) { }, } props.Visibility = []string{"//visibility:public"} - bazelProps := bazelProperties{ - &Bazel_module{ - Bp2build_available: proptools.BoolPtr(false), - }, - } - if txt.Bp2buildDefined { - moduleDir := ctx.ModuleDir() - if moduleDir == android.Bp2BuildTopLevel { - moduleDir = "" - } - label := fmt.Sprintf("//%s:%s", moduleDir, moduleName) - bazelProps.Label = &label - } - ctx.CreateModule(genrule.GenRuleFactory, &props, &bazelProps) + ctx.CreateModule(genrule.GenRuleFactory, &props) } func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) { @@ -221,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, &bp2buildNotAvailable) + ctx.CreateModule(android.FileGroupFactory, &props) } } @@ -315,7 +284,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, &bp2buildNotAvailable) + ctx.CreateModule(android.FileGroupFactory, &props) } func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) { @@ -323,43 +292,38 @@ func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_ tagSuffix := []string{".api.txt}", ".removed-api.txt}"} distFilename := []string{"android.txt", "android-removed.txt"} - bp2BuildDefined := []bool{true, false} for i, f := range []string{"current.txt", "removed.txt"} { textFiles = append(textFiles, MergedTxtDefinition{ - TxtFilename: f, - DistFilename: distFilename[i], - BaseTxt: ":non-updatable-" + f, - Modules: bootclasspath, - ModuleTag: "{.public" + tagSuffix[i], - Scope: "public", - Bp2buildDefined: bp2BuildDefined[i], + TxtFilename: f, + DistFilename: distFilename[i], + BaseTxt: ":non-updatable-" + f, + Modules: bootclasspath, + ModuleTag: "{.public" + tagSuffix[i], + Scope: "public", }) textFiles = append(textFiles, MergedTxtDefinition{ - TxtFilename: f, - DistFilename: distFilename[i], - BaseTxt: ":non-updatable-system-" + f, - Modules: bootclasspath, - ModuleTag: "{.system" + tagSuffix[i], - Scope: "system", - Bp2buildDefined: bp2BuildDefined[i], + TxtFilename: f, + DistFilename: distFilename[i], + BaseTxt: ":non-updatable-system-" + f, + Modules: bootclasspath, + ModuleTag: "{.system" + tagSuffix[i], + Scope: "system", }) textFiles = append(textFiles, MergedTxtDefinition{ - TxtFilename: f, - DistFilename: distFilename[i], - BaseTxt: ":non-updatable-module-lib-" + f, - Modules: bootclasspath, - ModuleTag: "{.module-lib" + tagSuffix[i], - Scope: "module-lib", - Bp2buildDefined: bp2BuildDefined[i], + TxtFilename: f, + DistFilename: distFilename[i], + BaseTxt: ":non-updatable-module-lib-" + f, + Modules: bootclasspath, + ModuleTag: "{.module-lib" + tagSuffix[i], + Scope: "module-lib", }) textFiles = append(textFiles, MergedTxtDefinition{ - TxtFilename: f, - DistFilename: distFilename[i], - BaseTxt: ":non-updatable-system-server-" + f, - Modules: system_server_classpath, - ModuleTag: "{.system-server" + tagSuffix[i], - Scope: "system-server", - Bp2buildDefined: bp2BuildDefined[i], + TxtFilename: f, + DistFilename: distFilename[i], + BaseTxt: ":non-updatable-system-server-" + f, + Modules: system_server_classpath, + ModuleTag: "{.system-server" + tagSuffix[i], + Scope: "system-server", }) } for _, txt := range textFiles { @@ -446,55 +410,9 @@ 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.Bp2buildMutatorContext) { - basePrefix := "non-updatable" - 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, suffix := range scopeToSuffix { - 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 deleted file mode 100644 index 70f2162348ad..000000000000 --- a/api/api_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// 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" - "android/soong/java" -) - -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) { - ctx.RegisterModuleType("java_defaults", java.DefaultsFactory) - ctx.RegisterModuleType("java_sdk_library", java.SdkLibraryFactory) - ctx.RegisterModuleType("filegroup", android.FileGroupFactory) - }) -} - -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"], -} - -java_sdk_library { - name: "bcp", - srcs: ["a.java", "b.java"], - shared_library: false, -} -java_sdk_library { - name: "ssc", - srcs: ["a.java", "b.java"], - shared_library: false, -} -filegroup { - name: "non-updatable-current.txt", - srcs: ["current.txt"], -} -filegroup { - name: "non-updatable-system-current.txt", - srcs: ["system-current.txt"], -} -filegroup { - name: "non-updatable-module-lib-current.txt", - srcs: ["system-removed.txt"], -} -filegroup { - name: "non-updatable-system-server-current.txt", - srcs: ["system-lint-baseline.txt"], -} -`, - Filesystem: map[string]string{ - "a/Android.bp": ` - java_defaults { - name: "android.jar_defaults", - } - `, - "api/current.txt": "", - "api/removed.txt": "", - "api/system-current.txt": "", - "api/system-removed.txt": "", - "api/test-current.txt": "", - "api/test-removed.txt": "", - }, - StubbedBuildDefinitions: []string{"bcp", "ssc", "non-updatable-current.txt", "non-updatable-system-current.txt", "non-updatable-module-lib-current.txt", "non-updatable-system-server-current.txt"}, - ExpectedHandcraftedModules: []string{"foo-current.txt", "foo-system-current.txt", "foo-module-lib-current.txt", "foo-system-server-current.txt"}, - ExpectedBazelTargets: []string{ - bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-current.txt", bp2build.AttrNameToString{ - "scope": `"public"`, - "base": `":non-updatable-current.txt"`, - "deps": `[":bcp"]`, - }), - bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-current.txt", bp2build.AttrNameToString{ - "scope": `"system"`, - "base": `":non-updatable-system-current.txt"`, - "deps": `[":bcp"]`, - }), - bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-module-lib-current.txt", bp2build.AttrNameToString{ - "scope": `"module-lib"`, - "base": `":non-updatable-module-lib-current.txt"`, - "deps": `[":bcp"]`, - }), - bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-server-current.txt", bp2build.AttrNameToString{ - "scope": `"system-server"`, - "base": `":non-updatable-system-server-current.txt"`, - "deps": `[":ssc"]`, - }), - }, - }) -} diff --git a/api/go.mod b/api/go.mod index f8bb1c01cd96..445a6f4a5ec6 100644 --- a/api/go.mod +++ b/api/go.mod @@ -6,7 +6,5 @@ require ( android/soong v0.0.0 github.com/google/blueprint v0.0.0 google.golang.org/protobuf v0.0.0 - prebuilts/bazel/common/proto/analysis_v2 v0.0.0 - prebuilts/bazel/common/proto/build v0.0.0 go.starlark.net v0.0.0 ) diff --git a/api/go.work b/api/go.work index aa2d2b1cb461..edd002e7efba 100644 --- a/api/go.work +++ b/api/go.work @@ -13,7 +13,5 @@ replace ( google.golang.org/protobuf v0.0.0 => ../../../external/golang-protobuf github.com/google/blueprint v0.0.0 => ../../../build/blueprint github.com/google/go-cmp v0.0.0 => ../../../external/go-cmp - prebuilts/bazel/common/proto/analysis_v2 v0.0.0 => ../../../prebuilts/bazel/common/proto/analysis_v2 - prebuilts/bazel/common/proto/build v0.0.0 => ../../../prebuilts/bazel/common/proto/build go.starlark.net v0.0.0 => ../../../external/starlark-go ) diff --git a/core/api/current.txt b/core/api/current.txt index f245b5c38e19..83e3fabe475b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -39071,6 +39071,7 @@ package android.security { public class NetworkSecurityPolicy { method public static android.security.NetworkSecurityPolicy getInstance(); + method @FlaggedApi("android.security.certificate_transparency_configuration") public boolean isCertificateTransparencyVerificationRequired(@NonNull String); method public boolean isCleartextTrafficPermitted(); method public boolean isCleartextTrafficPermitted(String); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 39f2737dc880..a3cd3dc87db3 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2,6 +2,7 @@ package android { public static final class Manifest.permission { + field @FlaggedApi("com.android.server.accessibility.motion_event_observing") public static final String ACCESSIBILITY_MOTION_EVENT_OBSERVING = "android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"; field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; @@ -99,6 +100,8 @@ package android.accessibilityservice { public class AccessibilityServiceInfo implements android.os.Parcelable { method @NonNull public android.content.ComponentName getComponentName(); + method @FlaggedApi("android.view.accessibility.motion_event_observing") public int getObservedMotionEventSources(); + method @FlaggedApi("android.view.accessibility.motion_event_observing") public void setObservedMotionEventSources(int); } } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 8ad6ea207665..fc342fa3431a 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -20,6 +20,7 @@ import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHt import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage; import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -53,6 +54,7 @@ import android.view.InputDevice; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.Flags; import com.android.internal.R; import com.android.internal.compat.IPlatformCompat; @@ -630,7 +632,8 @@ public class AccessibilityServiceInfo implements Parcelable { InputDevice.SOURCE_TOUCH_NAVIGATION, InputDevice.SOURCE_ROTARY_ENCODER, InputDevice.SOURCE_JOYSTICK, - InputDevice.SOURCE_SENSOR + InputDevice.SOURCE_SENSOR, + InputDevice.SOURCE_TOUCHSCREEN }) @Retention(RetentionPolicy.SOURCE) public @interface MotionEventSources {} @@ -642,6 +645,8 @@ public class AccessibilityServiceInfo implements Parcelable { @MotionEventSources private int mMotionEventSources = 0; + private int mObservedMotionEventSources = 0; + /** * Creates a new instance. */ @@ -817,6 +822,9 @@ public class AccessibilityServiceInfo implements Parcelable { mInteractiveUiTimeout = other.mInteractiveUiTimeout; flags = other.flags; mMotionEventSources = other.mMotionEventSources; + if (Flags.motionEventObserving()) { + setObservedMotionEventSources(other.mObservedMotionEventSources); + } // NOTE: Ensure that only properties that are safe to be modified by the service itself // are included here (regardless of hidden setters, etc.). } @@ -1024,16 +1032,75 @@ public class AccessibilityServiceInfo implements Parcelable { */ public void setMotionEventSources(@MotionEventSources int motionEventSources) { mMotionEventSources = motionEventSources; + mObservedMotionEventSources = 0; + } + + /** + * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility service + * wants to observe generic {@link android.view.MotionEvent}s from if it has already requested + * to listen to them using {@link #setMotionEventSources(int)}. Events from these sources will + * be sent to the rest of the input pipeline without being consumed by accessibility services. + * This service will still be able to see them. + * + * <p><strong>Note:</strong> you will need to call this function every time you call {@link + * #setMotionEventSources(int)}. Calling {@link #setMotionEventSources(int)} clears the list of + * observed motion event sources for this service. + * + * <p><strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits + * that complicate bitwise flag removal operations. To remove a specific source you should + * rebuild the entire value using bitwise OR operations on the individual source constants. + * + * <p>Including an {@link android.view.InputDevice} source that does not send {@link + * android.view.MotionEvent}s is effectively a no-op for that source, since you will not receive + * any events from that source. + * + * <p><strong>Note:</strong> Calling this function with a source that has not been listened to + * using {@link #setMotionEventSources(int)} will throw an exception. + * + * @see AccessibilityService#onMotionEvent + * @see #MotionEventSources + * @see #setMotionEventSources(int) + * @hide + */ + @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING) + @TestApi + public void setObservedMotionEventSources(int observedMotionEventSources) { + // Confirm that any sources requested here have already been requested for listening. + if ((observedMotionEventSources & ~mMotionEventSources) != 0) { + String message = + String.format( + "Requested motion event sources for listening = 0x%x but requested" + + " motion event sources for observing = 0x%x.", + mMotionEventSources, observedMotionEventSources); + throw new IllegalArgumentException(message); + } + mObservedMotionEventSources = observedMotionEventSources; + } + + /** + * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility + * service wants to observe generic {@link android.view.MotionEvent}s from if it has already + * requested to listen to them using {@link #setMotionEventSources(int)}. Events from these + * sources will be sent to the rest of the input pipeline without being consumed by + * accessibility services. This service will still be able to see them. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING) + @MotionEventSources + @TestApi + public int getObservedMotionEventSources() { + return mObservedMotionEventSources; } /** * The localized summary of the accessibility service. - * <p> - * <strong>Statically set from - * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> - * </p> - * @return The localized summary if available, and {@code null} if a summary - * has not been provided. + * + * <p><strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA + * meta-data}.</strong> + * + * @return The localized summary if available, and {@code null} if a summary has not been + * provided. */ public CharSequence loadSummary(PackageManager packageManager) { if (mSummaryResId == 0) { @@ -1260,6 +1327,7 @@ public class AccessibilityServiceInfo implements Parcelable { parcel.writeString(mTileServiceName); parcel.writeInt(mIntroResId); parcel.writeInt(mMotionEventSources); + parcel.writeInt(mObservedMotionEventSources); } private void initFromParcel(Parcel parcel) { @@ -1285,6 +1353,8 @@ public class AccessibilityServiceInfo implements Parcelable { mTileServiceName = parcel.readString(); mIntroResId = parcel.readInt(); mMotionEventSources = parcel.readInt(); + // use the setter here because it throws an exception for invalid values. + setObservedMotionEventSources(parcel.readInt()); } @Override diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index ffed40538702..4a9fa9e63bf9 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6884,8 +6884,8 @@ public class Activity extends ContextThemeWrapper * application package was involved. * * <p>If called while inside the handling of {@link #onNewIntent}, this function will - * return the referrer that submitted that new intent to the activity. Otherwise, it - * always returns the referrer of the original Intent.</p> + * return the referrer that submitted that new intent to the activity only after + * {@link #setIntent(Intent)} is called with the provided intent.</p> * * <p>Note that this is <em>not</em> a security feature -- you can not trust the * referrer information, applications can spoof it.</p> diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index a510c7704751..013bcddbb7f3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2205,9 +2205,6 @@ public class Notification implements Parcelable private void visitUris(@NonNull Consumer<Uri> visitor) { visitIconUri(visitor, getIcon()); - if (actionIntent != null) { - actionIntent.visitUris(visitor); - } } @Override @@ -2901,21 +2898,6 @@ public class Notification implements Parcelable } } - // allPendingIntents should contain all associated intents after parcelling, but it may also - // contain intents added by the app to extras for their own purposes. We only care about - // checking the intents known and used by system_server, to avoid the confused deputy issue. - List<PendingIntent> pendingIntents = Arrays.asList(contentIntent, deleteIntent, - fullScreenIntent); - for (PendingIntent intent : pendingIntents) { - if (intent != null) { - intent.visitUris(visitor); - } - } - - if (mBubbleMetadata != null) { - mBubbleMetadata.visitUris(visitor); - } - if (extras != null) { visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class)); visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class)); @@ -2987,28 +2969,15 @@ public class Notification implements Parcelable callPerson.visitUris(visitor); } visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class)); + } - // Extras for MediaStyle. - PendingIntent deviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT, - PendingIntent.class); - if (deviceIntent != null) { - deviceIntent.visitUris(visitor); - } - - if (extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { - WearableExtender extender = new WearableExtender(this); - extender.visitUris(visitor); - } - - if (extras.containsKey(TvExtender.EXTRA_TV_EXTENDER)) { - TvExtender extender = new TvExtender(this); - extender.visitUris(visitor); - } + if (mBubbleMetadata != null) { + visitIconUri(visitor, mBubbleMetadata.getIcon()); + } - if (extras.containsKey(CarExtender.EXTRA_CAR_EXTENDER)) { - CarExtender extender = new CarExtender(this); - extender.visitUris(visitor); - } + if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { + WearableExtender extender = new WearableExtender(this); + extender.visitUris(visitor); } } @@ -10589,16 +10558,6 @@ public class Notification implements Parcelable } } - private void visitUris(@NonNull Consumer<Uri> visitor) { - visitIconUri(visitor, getIcon()); - if (mPendingIntent != null) { - mPendingIntent.visitUris(visitor); - } - if (mDeleteIntent != null) { - mDeleteIntent.visitUris(visitor); - } - } - /** * Builder to construct a {@link BubbleMetadata} object. */ @@ -11797,9 +11756,6 @@ public class Notification implements Parcelable } private void visitUris(@NonNull Consumer<Uri> visitor) { - if (mDisplayIntent != null) { - mDisplayIntent.visitUris(visitor); - } for (Action action : mActions) { action.visitUris(visitor); } @@ -11952,19 +11908,12 @@ public class Notification implements Parcelable /** * Returns the unread conversation conveyed by this notification. - * * @see #setUnreadConversation(UnreadConversation) */ public UnreadConversation getUnreadConversation() { return mUnreadConversation; } - private void visitUris(@NonNull Consumer<Uri> visitor) { - if (mUnreadConversation != null) { - mUnreadConversation.visitUris(visitor); - } - } - /** * A class which holds the unread messages from a conversation. */ @@ -12116,16 +12065,7 @@ public class Notification implements Parcelable onRead, participants, b.getLong(KEY_TIMESTAMP)); } - - private void visitUris(@NonNull Consumer<Uri> visitor) { - if (mReadPendingIntent != null) { - mReadPendingIntent.visitUris(visitor); - } - if (mReplyPendingIntent != null) { - mReplyPendingIntent.visitUris(visitor); - } - } - } + }; /** * Builder class for {@link CarExtender.UnreadConversation} objects. @@ -12448,15 +12388,6 @@ public class Notification implements Parcelable public boolean isSuppressShowOverApps() { return mSuppressShowOverApps; } - - private void visitUris(@NonNull Consumer<Uri> visitor) { - if (mContentIntent != null) { - mContentIntent.visitUris(visitor); - } - if (mDeleteIntent != null) { - mDeleteIntent.visitUris(visitor); - } - } } /** diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 0261f0a02174..62209b0fd27d 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -44,8 +44,6 @@ import android.content.IntentSender; import android.content.pm.PackageManager.ResolveInfoFlagsBits; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -71,7 +69,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; -import java.util.function.Consumer; /** * A description of an Intent and target action to perform with it. Instances @@ -1463,21 +1460,6 @@ public final class PendingIntent implements Parcelable { return sb.toString(); } - /** - * See {@link Intent#visitUris(Consumer)}. - * - * @hide - */ - public void visitUris(@NonNull Consumer<Uri> visitor) { - if (android.app.Flags.visitRiskyUris()) { - Intent intent = Binder.withCleanCallingIdentity(this::getIntent); - - if (intent != null) { - intent.visitUris(visitor); - } - } - } - /** @hide */ public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 90a265937082..4a6349b1b02f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9381,7 +9381,7 @@ public class DevicePolicyManager { @Deprecated @SystemApi @RequiresPermission(MANAGE_DEVICE_ADMINS) - public boolean setActiveProfileOwner(@NonNull ComponentName admin, @Deprecated String ownerName) + public boolean setActiveProfileOwner(@NonNull ComponentName admin, String ownerName) throws IllegalArgumentException { throwIfParentInstance("setActiveProfileOwner"); if (mService != null) { diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index e4a03c596254..d5b5f40a6980 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -37,6 +37,7 @@ import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -709,7 +710,9 @@ public final class CompanionDeviceManager { IntentSender intentSender = mService .requestNotificationAccess(component, mContext.getUserId()) .getIntentSender(); - mContext.startIntentSender(intentSender, null, 0, 0, 0); + mContext.startIntentSender(intentSender, null, 0, 0, 0, + ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (IntentSender.SendIntentException e) { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 183b9b0000d2..7af0be3b3e75 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -101,7 +101,6 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.TimeZone; -import java.util.function.Consumer; /** * An intent is an abstract description of an operation to be performed. It @@ -8148,27 +8147,6 @@ public class Intent implements Parcelable, Cloneable { } } - /** - * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission - * grants will need to be issued to ensure the recipient of this object is able to render its - * contents. - * See b/281044385 for more context and examples about what happens when this isn't done - * correctly. - * - * @hide - */ - public void visitUris(@NonNull Consumer<Uri> visitor) { - if (android.app.Flags.visitRiskyUris()) { - visitor.accept(mData); - if (mSelector != null) { - mSelector.visitUris(visitor); - } - if (mOriginalIntent != null) { - mOriginalIntent.visitUris(visitor); - } - } - } - public static Intent getIntentOld(String uri) throws URISyntaxException { Intent intent = getIntentOld(uri, 0); intent.mLocalFlags |= LOCAL_FLAG_FROM_URI; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e2243292ab8d..4d75a5147d6e 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -6302,6 +6302,11 @@ public abstract class PackageManager { /** * Check whether a particular package has been granted a particular * permission. + * <p> + * <strong>Note: </strong>This API returns the underlying permission state + * as-is and is mostly intended for permission managing system apps. To + * perform an access check for a certain app, please use the + * {@link Context#checkPermission} APIs instead. * * @param permName The name of the permission you are checking for. * @param packageName The name of the package you are checking against. diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 6c6b33b8d716..57025c25f97b 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -49,3 +49,10 @@ flag { description: "Add support to unlock the private space using biometrics" bug: "312184187" } + +flag { + name: "support_autolock_for_private_space" + namespace: "profile_experiences" + description: "Add support to lock private space automatically after a time period" + bug: "303201022" +} diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 8196bf505e02..20b09326e9c0 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -40,7 +40,6 @@ import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; -import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; import android.util.Pair; @@ -722,6 +721,7 @@ public final class CameraExtensionCharacteristics { switch(format) { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: + case ImageFormat.JPEG_R: break; default: throw new IllegalArgumentException("Unsupported format: " + format); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 16ffaef03121..10a8022df0ea 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1657,6 +1657,7 @@ public abstract class BatteryStats { */ public abstract CpuScalingPolicies getCpuScalingPolicies(); + @android.ravenwood.annotation.RavenwoodKeepWholeClass public final static class HistoryTag { public static final int HISTORY_TAG_POOL_OVERFLOW = -1; @@ -1713,6 +1714,7 @@ public abstract class BatteryStats { * Optional detailed information that can go into a history step. This is typically * generated each time the battery level changes. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public final static class HistoryStepDetails { // Time (in 1/100 second) spent in user space and the kernel since the last step. public int userTime; @@ -1797,6 +1799,7 @@ public abstract class BatteryStats { /** * An extension to the history item describing a proc state change for a UID. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class ProcessStateChange { public int uid; public @BatteryConsumer.ProcessState int processState; @@ -1850,6 +1853,7 @@ public abstract class BatteryStats { /** * Battery history record. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class HistoryItem { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public HistoryItem next; diff --git a/core/java/android/os/ConditionVariable.java b/core/java/android/os/ConditionVariable.java index a13eaa6f19bf..b5ed53bf0e5e 100644 --- a/core/java/android/os/ConditionVariable.java +++ b/core/java/android/os/ConditionVariable.java @@ -29,6 +29,7 @@ package android.os; * This class uses itself as the object to wait on, so if you wait() * or notify() on a ConditionVariable, the results are undefined. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ConditionVariable { private volatile boolean mCondition; diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index f2930fe45295..8e860c35388d 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -465,9 +465,7 @@ public final class Parcel { private static native byte[] nativeMarshall(long nativePtr); private static native void nativeUnmarshall( long nativePtr, byte[] data, int offset, int length); - @RavenwoodThrow private static native int nativeCompareData(long thisNativePtr, long otherNativePtr); - @RavenwoodThrow private static native boolean nativeCompareDataInRange( long ptrA, int offsetA, long ptrB, int offsetB, int length); private static native void nativeAppendFrom( diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 69d86a6604ad..437668c9a7de 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -44,3 +44,10 @@ flag { description: "Enables the independent keyboard vibration settings feature" bug: "289107579" } + +flag { + namespace: "haptics" + name: "adaptive_haptics_enabled" + description: "Enables the adaptive haptics feature" + bug: "305961689" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2ed994b503c9..2cc56d838b79 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8301,6 +8301,17 @@ public final class Settings { public static final String ACCESSIBILITY_BUTTON_TARGETS = "accessibility_button_targets"; /** + * Setting specifying the accessibility services, accessibility shortcut targets, + * or features to be toggled via a tile in the quick settings panel. + * + * <p> This is a colon-separated string list which contains the flattened + * {@link ComponentName} and the class name of a system class implementing a supported + * accessibility feature. + * @hide + */ + public static final String ACCESSIBILITY_QS_TARGETS = "accessibility_qs_targets"; + + /** * The system class name of magnification controller which is a target to be toggled via * accessibility shortcut or accessibility button. * diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java index 0c4eedab2fc0..e679d201c58f 100644 --- a/core/java/android/security/NetworkSecurityPolicy.java +++ b/core/java/android/security/NetworkSecurityPolicy.java @@ -16,6 +16,8 @@ package android.security; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.security.net.config.ApplicationConfig; @@ -26,9 +28,6 @@ import android.security.net.config.ManifestConfigSource; * * <p>Network stacks/components should honor this policy to make it possible to centrally control * the relevant aspects of network security behavior. - * - * <p>The policy currently consists of a single flag: whether cleartext network traffic is - * permitted. See {@link #isCleartextTrafficPermitted()}. */ public class NetworkSecurityPolicy { @@ -94,6 +93,22 @@ public class NetworkSecurityPolicy { } /** + * Returns {@code true} if Certificate Transparency information is required to be verified by + * the client in TLS connections to {@code hostname}. + * + * <p>See RFC6962 section 3.3 for more details. + * + * @param hostname hostname to check whether certificate transparency verification is required + * @return {@code true} if certificate transparency verification is required and {@code false} + * otherwise + */ + @FlaggedApi(Flags.FLAG_CERTIFICATE_TRANSPARENCY_CONFIGURATION) + public boolean isCertificateTransparencyVerificationRequired(@NonNull String hostname) { + return libcore.net.NetworkSecurityPolicy.getInstance() + .isCertificateTransparencyVerificationRequired(hostname); + } + + /** * Handle an update to the system or user certificate stores. * @hide */ diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 28ef70b14026..b56bef3c93a0 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -1,6 +1,13 @@ package: "android.security" flag { + name: "certificate_transparency_configuration" + namespace: "network_security" + description: "Enable certificate transparency setting in the network security config" + bug: "28746284" +} + +flag { name: "fsverity_api" namespace: "hardware_backed_security" description: "Feature flag for fs-verity API" diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java index 801ecebc616f..4cc870bfb47a 100644 --- a/core/java/android/security/net/config/ApplicationConfig.java +++ b/core/java/android/security/net/config/ApplicationConfig.java @@ -16,10 +16,15 @@ package android.security.net.config; +import static android.security.Flags.certificateTransparencyConfiguration; + +import android.annotation.NonNull; import android.util.Pair; + import java.util.HashSet; import java.util.Locale; import java.util.Set; + import javax.net.ssl.X509TrustManager; /** @@ -147,6 +152,22 @@ public final class ApplicationConfig { return getConfigForHostname(hostname).isCleartextTrafficPermitted(); } + /** + * Returns {@code true} if Certificate Transparency information is required to be verified by + * the client in TLS connections to {@code hostname}. + * + * <p>See RFC6962 section 3.3 for more details. + * + * @param hostname hostname to check whether certificate transparency verification is required + * @return {@code true} if certificate transparency verification is required and {@code false} + * otherwise + */ + public boolean isCertificateTransparencyVerificationRequired(@NonNull String hostname) { + return certificateTransparencyConfiguration() + ? getConfigForHostname(hostname).isCertificateTransparencyVerificationRequired() + : NetworkSecurityConfig.DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED; + } + public void handleTrustStorageUpdate() { synchronized(mLock) { // If the config is uninitialized then there is no work to be done to handle an update, diff --git a/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java b/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java index a708f5b57dc9..801b32b055aa 100644 --- a/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java +++ b/core/java/android/security/net/config/ConfigNetworkSecurityPolicy.java @@ -40,6 +40,6 @@ public class ConfigNetworkSecurityPolicy extends libcore.net.NetworkSecurityPoli @Override public boolean isCertificateTransparencyVerificationRequired(String hostname) { - return false; + return mConfig.isCertificateTransparencyVerificationRequired(hostname); } } diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java index 00872fb3b62d..129ae63ec9c0 100644 --- a/core/java/android/security/net/config/NetworkSecurityConfig.java +++ b/core/java/android/security/net/config/NetworkSecurityConfig.java @@ -38,9 +38,12 @@ public final class NetworkSecurityConfig { public static final boolean DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED = true; /** @hide */ public static final boolean DEFAULT_HSTS_ENFORCED = false; + /** @hide */ + public static final boolean DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED = false; private final boolean mCleartextTrafficPermitted; private final boolean mHstsEnforced; + private final boolean mCertificateTransparencyVerificationRequired; private final PinSet mPins; private final List<CertificatesEntryRef> mCertificatesEntryRefs; private Set<TrustAnchor> mAnchors; @@ -48,10 +51,15 @@ public final class NetworkSecurityConfig { private NetworkSecurityTrustManager mTrustManager; private final Object mTrustManagerLock = new Object(); - private NetworkSecurityConfig(boolean cleartextTrafficPermitted, boolean hstsEnforced, - PinSet pins, List<CertificatesEntryRef> certificatesEntryRefs) { + private NetworkSecurityConfig( + boolean cleartextTrafficPermitted, + boolean hstsEnforced, + boolean certificateTransparencyVerificationRequired, + PinSet pins, + List<CertificatesEntryRef> certificatesEntryRefs) { mCleartextTrafficPermitted = cleartextTrafficPermitted; mHstsEnforced = hstsEnforced; + mCertificateTransparencyVerificationRequired = certificateTransparencyVerificationRequired; mPins = pins; mCertificatesEntryRefs = certificatesEntryRefs; // Sort the certificates entry refs so that all entries that override pins come before @@ -104,6 +112,11 @@ public final class NetworkSecurityConfig { return mHstsEnforced; } + // TODO(b/28746284): add exceptions for user-added certificates and enterprise overrides. + public boolean isCertificateTransparencyVerificationRequired() { + return mCertificateTransparencyVerificationRequired; + } + public PinSet getPins() { return mPins; } @@ -208,6 +221,9 @@ public final class NetworkSecurityConfig { private boolean mHstsEnforced = DEFAULT_HSTS_ENFORCED; private boolean mCleartextTrafficPermittedSet = false; private boolean mHstsEnforcedSet = false; + private boolean mCertificateTransparencyVerificationRequired = + DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED; + private boolean mCertificateTransparencyVerificationRequiredSet = false; private Builder mParentBuilder; /** @@ -313,12 +329,35 @@ public final class NetworkSecurityConfig { return mCertificatesEntryRefs; } + Builder setCertificateTransparencyVerificationRequired(boolean required) { + mCertificateTransparencyVerificationRequired = required; + mCertificateTransparencyVerificationRequiredSet = true; + return this; + } + + private boolean getCertificateTransparencyVerificationRequired() { + if (mCertificateTransparencyVerificationRequiredSet) { + return mCertificateTransparencyVerificationRequired; + } + if (mParentBuilder != null) { + return mParentBuilder.getCertificateTransparencyVerificationRequired(); + } + return DEFAULT_CERTIFICATE_TRANSPARENCY_VERIFICATION_REQUIRED; + } + public NetworkSecurityConfig build() { boolean cleartextPermitted = getEffectiveCleartextTrafficPermitted(); boolean hstsEnforced = getEffectiveHstsEnforced(); + boolean certificateTransparencyVerificationRequired = + getCertificateTransparencyVerificationRequired(); PinSet pinSet = getEffectivePinSet(); List<CertificatesEntryRef> entryRefs = getEffectiveCertificatesEntryRefs(); - return new NetworkSecurityConfig(cleartextPermitted, hstsEnforced, pinSet, entryRefs); + return new NetworkSecurityConfig( + cleartextPermitted, + hstsEnforced, + certificateTransparencyVerificationRequired, + pinSet, + entryRefs); } } } diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java index 311a8d23b964..b1c14793bbbd 100644 --- a/core/java/android/security/net/config/XmlConfigSource.java +++ b/core/java/android/security/net/config/XmlConfigSource.java @@ -171,6 +171,11 @@ public class XmlConfigSource implements ConfigSource { return new Domain(domain, includeSubdomains); } + private boolean parseCertificateTransparency(XmlResourceParser parser) + throws IOException, XmlPullParserException, ParserException { + return parser.getAttributeBooleanValue(null, "enabled", false); + } + private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser, boolean defaultOverridePins) throws IOException, XmlPullParserException, ParserException { @@ -226,7 +231,6 @@ public class XmlConfigSource implements ConfigSource { boolean seenPinSet = false; boolean seenTrustAnchors = false; boolean defaultOverridePins = configType == CONFIG_DEBUG; - String configName = parser.getName(); int outerDepth = parser.getDepth(); // Add this builder now so that this builder occurs before any of its children. This // makes the final build pass easier. @@ -279,6 +283,15 @@ public class XmlConfigSource implements ConfigSource { "Nested domain-config not allowed in " + getConfigString(configType)); } builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType)); + } else if ("certificateTransparency".equals(tagName)) { + if (configType != CONFIG_BASE && configType != CONFIG_DOMAIN) { + throw new ParserException( + parser, + "certificateTransparency not allowed in " + + getConfigString(configType)); + } + builder.setCertificateTransparencyVerificationRequired( + parseCertificateTransparency(parser)); } else { XmlUtils.skipCurrentTag(parser); } diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index e057660961f6..0cc19fb70fbc 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -59,6 +59,13 @@ flag { } flag { + name: "motion_event_observing" + namespace: "accessibility" + description: "Allows accessibility services to intercept but not consume motion events from specified sources." + bug: "297595990" +} + +flag { namespace: "accessibility" name: "granular_scrolling" description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen" diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 6bc2a1368a91..bb49679453ac 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -38,6 +38,7 @@ import android.annotation.RequiresFeature; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.app.ActivityOptions; import android.app.assist.AssistStructure.ViewNode; import android.app.assist.AssistStructure.ViewNodeBuilder; import android.app.assist.AssistStructure.ViewNodeParcelable; @@ -4370,7 +4371,11 @@ public final class AutofillManager { if (afm != null) { afm.post(() -> { try { - afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0); + Bundle options = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) + .toBundle(); + afm.mContext.startIntentSender(intentSender, intent, 0, 0, 0, options); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "startIntentSender() failed for intent:" + intentSender, e); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a31a610ab523..da3134856731 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1015,11 +1015,6 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_PENDING_INTENT_TEMPLATE_TAG; } - - @Override - public void visitUris(@NonNull Consumer<Uri> visitor) { - mPendingIntentTemplate.visitUris(visitor); - } } /** @@ -1434,7 +1429,9 @@ public class RemoteViews implements Parcelable, Filter { @Override public void visitUris(@NonNull Consumer<Uri> visitor) { - mIntent.visitUris(visitor); + // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated + // visitUris method in the Intent class, since it can contain other intents. Otherwise, + // the basic thing to do here would be just visitor.accept(intent.getData()). } } @@ -1514,7 +1511,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public void visitUris(@NonNull Consumer<Uri> visitor) { - mResponse.visitUris(visitor); + // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse. } } @@ -1563,11 +1560,6 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG; } - - @Override - public void visitUris(@NonNull Consumer<Uri> visitor) { - mPendingIntent.visitUris(visitor); - } } /** @@ -1641,7 +1633,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public void visitUris(@NonNull Consumer<Uri> visitor) { - mResponse.visitUris(visitor); + // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse. } } @@ -2202,10 +2194,6 @@ public class RemoteViews implements Parcelable, Filter { final Icon icon = (Icon) getParameterValue(null); if (icon != null) visitIconUri(icon, visitor); break; - case INTENT: - final Intent intent = (Intent) getParameterValue(null); - if (intent != null) intent.visitUris(visitor); - break; // TODO(b/281044385): Should we do anything about type BUNDLE? } } @@ -6995,20 +6983,6 @@ public class RemoteViews implements Parcelable, Filter { mElementNames = parcel.createStringArrayList(); } - /** - * See {@link RemoteViews#visitUris(Consumer)}. - * - * @hide - */ - public void visitUris(@NonNull Consumer<Uri> visitor) { - if (mPendingIntent != null) { - mPendingIntent.visitUris(visitor); - } - if (mFillIntent != null) { - mFillIntent.visitUris(visitor); - } - } - private void handleViewInteraction( View v, InteractionHandler handler) { diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 7f65c52d01b8..07beb114898d 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -16,7 +16,7 @@ flag { flag { name: "defer_display_updates" - namespace: "window_manager" + namespace: "windowing_frontend" description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running" bug: "259220649" is_fixed_read_only: true diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 7d78f299c625..0be98040af73 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -72,6 +72,7 @@ import java.util.concurrent.locks.ReentrantLock; * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by * locks on BatteryStatsImpl object. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BatteryStatsHistory { private static final boolean DEBUG = false; private static final String TAG = "BatteryStatsHistory"; @@ -259,6 +260,7 @@ public class BatteryStatsHistory { * until the first change occurs. */ @VisibleForTesting + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static class TraceDelegate { // Note: certain tests currently run as platform_app which is not allowed // to set debug system properties. To ensure that system properties are set @@ -391,10 +393,18 @@ public class BatteryStatsHistory { public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize, HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, MonotonicClock monotonicClock) { + this(maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, clock, monotonicClock, + new TraceDelegate()); + } + + @VisibleForTesting + public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize, + HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, + MonotonicClock monotonicClock, TraceDelegate traceDelegate) { mMaxHistoryFiles = maxHistoryFiles; mMaxHistoryBufferSize = maxHistoryBufferSize; mStepDetailsCalculator = stepDetailsCalculator; - mTracer = new TraceDelegate(); + mTracer = traceDelegate; mClock = clock; mMonotonicClock = monotonicClock; @@ -2096,6 +2106,7 @@ public class BatteryStatsHistory { * fewer bytes. It is a bit more expensive than just writing the long into the parcel, * but at scale saves a lot of storage and allows recording of longer battery history. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static final class VarintParceler { /** * Writes an array of longs into Parcel using the varint format, see diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index 6bd5898b1637..2dffe15dc4be 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -28,6 +28,7 @@ import java.util.Iterator; /** * An iterator for {@link BatteryStats.HistoryItem}'s. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>, AutoCloseable { private static final boolean DEBUG = false; diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 1a7efac82278..56263fb924ea 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -41,6 +41,7 @@ import java.util.Objects; * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for * details. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class PowerStats { private static final String TAG = "PowerStats"; @@ -67,6 +68,7 @@ public final class PowerStats { * This descriptor is used for storing PowerStats and can also be used by power models * to adjust the algorithm in accordance with the stats available on the device. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass public static class Descriptor { public static final String XML_TAG_DESCRIPTOR = "descriptor"; private static final String XML_ATTR_ID = "id"; diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 3887dd7a753f..9ca1849dedc2 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -99,6 +99,7 @@ message SecureSettingsProto { optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto accessibility_magnification_two_finger_triple_tap_enabled = 53 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto qs_targets = 54 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index dd93586c340b..c6a241f2fa62 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4806,6 +4806,13 @@ <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" android:protectionLevel="signature" /> + <!-- @FlaggedApi("com.android.server.accessibility.motion_event_observing") + @hide + @TestApi + Allows an accessibility service to observe motion events without consuming them. --> + <permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" + android:protectionLevel="signature" /> + <!-- @hide Allows an application to collect frame statistics --> <permission android:name="android.permission.FRAME_STATS" android:protectionLevel="signature" /> diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java index 8c231de5598f..e7b5dff60110 100644 --- a/core/tests/coretests/src/android/os/BundleTest.java +++ b/core/tests/coretests/src/android/os/BundleTest.java @@ -197,7 +197,6 @@ public class BundleTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_bothParcelled_same() { Bundle bundle1 = new Bundle(); bundle1.putString("StringKey", "S"); @@ -215,7 +214,6 @@ public class BundleTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_bothParcelled_different() { Bundle bundle1 = new Bundle(); bundle1.putString("StringKey", "S"); @@ -247,7 +245,6 @@ public class BundleTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_lazyValues() { Parcelable p1 = new CustomParcelable(13, "Tiramisu"); Parcelable p2 = new CustomParcelable(13, "Tiramisu"); @@ -281,7 +278,6 @@ public class BundleTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_lazyValuesWithIdenticalParcels_returnsTrue() { Parcelable p1 = new CustomParcelable(13, "Tiramisu"); Parcelable p2 = new CustomParcelable(13, "Tiramisu"); diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java index 8cd6773936ef..851e61259241 100644 --- a/core/tests/coretests/src/android/os/MessageQueueTest.java +++ b/core/tests/coretests/src/android/os/MessageQueueTest.java @@ -16,6 +16,7 @@ package android.os; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.MediumTest; @@ -153,6 +154,7 @@ public class MessageQueueTest { @Test @MediumTest + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testFieldIntegrity() throws Exception { TestHandlerThread tester = new TestFieldIntegrityHandler() { diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index 5bbd2219e2f0..26f6d696768a 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -132,7 +132,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenSameData() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -169,7 +168,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenDifferentData() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -186,7 +184,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenLimitOutOfBounds_throws() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -213,7 +210,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenLengthZero() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -232,7 +228,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenNegativeLength_throws() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); @@ -248,7 +243,6 @@ public class ParcelTest { } @Test - @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenNegativeOffset_throws() { Parcel pA = Parcel.obtain(); int iA = pA.dataPosition(); diff --git a/core/tests/coretests/src/android/util/SparseSetArrayTest.java b/core/tests/coretests/src/android/util/SparseSetArrayTest.java index 1df1090e0343..1c72185ea93c 100644 --- a/core/tests/coretests/src/android/util/SparseSetArrayTest.java +++ b/core/tests/coretests/src/android/util/SparseSetArrayTest.java @@ -37,6 +37,7 @@ public class SparseSetArrayTest { public final RavenwoodRule mRavenwood = new RavenwoodRule(); @Test + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testAddAll() { final SparseSetArray<Integer> sparseSetArray = new SparseSetArray<>(); diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 15c90474c017..c8ea3742b3aa 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -34,6 +34,9 @@ import android.app.PendingIntent; import android.appwidget.AppWidgetHostView; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AsyncTask; @@ -834,33 +837,6 @@ public class RemoteViewsTest { } @Test - public void visitUris_intents() { - RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test); - - Uri fillIntentUri = Uri.parse("content://intent/fill"); - views.setOnCheckedChangeResponse( - R.id.layout, - RemoteViews.RemoteResponse.fromFillInIntent(new Intent("action", fillIntentUri))); - - Uri pendingIntentUri = Uri.parse("content://intent/pending"); - PendingIntent pendingIntent = getPendingIntentWithUri(pendingIntentUri); - views.setOnClickResponse( - R.id.layout, - RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent)); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - views.visitUris(visitor); - verify(visitor, times(1)).accept(eq(fillIntentUri)); - verify(visitor, times(1)).accept(eq(pendingIntentUri)); - } - - private PendingIntent getPendingIntentWithUri(Uri uri) { - return PendingIntent.getActivity(mContext, 0, - new Intent("action", uri), - PendingIntent.FLAG_IMMUTABLE); - } - - @Test public void layoutInflaterFactory_nothingSet_returnsNull() { final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); assertNull(rv.getLayoutInflaterFactory()); diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java index f34b185b1c5a..c9536b9b8129 100644 --- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java @@ -57,6 +57,16 @@ public class LongArrayMultiStateCounterTest { } @Test + public void setValue() { + LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); + + counter.setValues(0, new long[]{1, 2, 3, 4}); + counter.setValues(1, new long[]{5, 6, 7, 8}); + assertCounts(counter, 0, new long[]{1, 2, 3, 4}); + assertCounts(counter, 1, new long[]{5, 6, 7, 8}); + } + + @Test public void setEnabled() { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); counter.setState(0, 1000); diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java index e8246c83e086..ac659e1bc593 100644 --- a/core/tests/utiltests/src/android/util/TimeUtilsTest.java +++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java @@ -18,8 +18,12 @@ package android.util; import static org.junit.Assert.assertEquals; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.runner.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,6 +33,9 @@ import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) public class TimeUtilsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + public static final long SECOND_IN_MILLIS = 1000; public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; @@ -78,6 +85,7 @@ public class TimeUtilsTest { } @Test + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testDumpTime() { assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> { TimeUtils.dumpTime(pw, 1672556400000L); @@ -91,6 +99,7 @@ public class TimeUtilsTest { } @Test + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testFormatForLogging() { assertEquals("unknown", TimeUtils.formatForLogging(0)); assertEquals("unknown", TimeUtils.formatForLogging(-1)); @@ -99,6 +108,7 @@ public class TimeUtilsTest { } @Test + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testLogTimeOfDay() { assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L)); } diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS index f0ed6ee5cb67..e346b51a4f19 100644 --- a/libs/WindowManager/Shell/OWNERS +++ b/libs/WindowManager/Shell/OWNERS @@ -1,4 +1,4 @@ xutan@google.com # Give submodule owners in shell resource approval -per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com +per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index b1b196d40357..fe65fdd30e48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -16,6 +16,7 @@ package com.android.wm.shell; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; @@ -168,6 +169,13 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private final Object mLock = new Object(); private StartingWindowController mStartingWindow; + /** Overlay surface for home root task */ + private final SurfaceControl mHomeTaskOverlayContainer = new SurfaceControl.Builder() + .setName("home_task_overlay_container") + .setContainerLayer() + .setHidden(false) + .build(); + /** * In charge of showing compat UI. Can be {@code null} if the device doesn't support size * compat or if this isn't the main {@link ShellTaskOrganizer}. @@ -428,6 +436,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } + /** + * Returns a surface which can be used to attach overlays to the home root task + */ + @NonNull + public SurfaceControl getHomeTaskOverlayContainer() { + return mHomeTaskOverlayContainer; + } + @Override public void addStartingWindow(StartingWindowInfo info) { if (mStartingWindow != null) { @@ -485,6 +501,15 @@ public class ShellTaskOrganizer extends TaskOrganizer implements if (mUnfoldAnimationController != null) { mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash()); } + + if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { + ProtoLog.v(WM_SHELL_TASK_ORG, "Adding overlay to home task"); + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setLayer(mHomeTaskOverlayContainer, Integer.MAX_VALUE); + t.reparent(mHomeTaskOverlayContainer, info.getLeash()); + t.apply(); + } + notifyLocusVisibilityIfNeeded(info.getTaskInfo()); notifyCompatUI(info.getTaskInfo(), listener); mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo())); @@ -579,6 +604,12 @@ public class ShellTaskOrganizer extends TaskOrganizer implements notifyCompatUI(taskInfo, null /* taskListener */); // Notify the recent tasks that a task has been removed mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo)); + if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.reparent(mHomeTaskOverlayContainer, null); + t.apply(); + ProtoLog.v(WM_SHELL_TASK_ORG, "Removing overlay surface"); + } if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) { // Preemptively clean up the leash only if shell transitions are not enabled diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 689323b725ae..7f34ee0cdd3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -78,34 +78,37 @@ public class BubbleBarAnimationHelper { mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + BubbleBarExpandedView bbev = getExpandedView(); + if (bbev != null) { // We need to be Z ordered on top in order for alpha animations to work. - mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(true); - mExpandedBubble.getBubbleBarExpandedView().setAnimating(true); + bbev.setSurfaceZOrderedOnTop(true); + bbev.setAnimating(true); } } @Override public void onAnimationEnd(Animator animation) { - if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + BubbleBarExpandedView bbev = getExpandedView(); + if (bbev != null) { // The surface needs to be Z ordered on top for alpha values to work on the // TaskView, and if we're temporarily hidden, we are still on the screen // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha // = 0f remains in effect. if (mIsExpanded) { - mExpandedBubble.getBubbleBarExpandedView().setSurfaceZOrderedOnTop(false); + bbev.setSurfaceZOrderedOnTop(false); } - mExpandedBubble.getBubbleBarExpandedView().setContentVisibility(mIsExpanded); - mExpandedBubble.getBubbleBarExpandedView().setAnimating(false); + bbev.setContentVisibility(mIsExpanded); + bbev.setAnimating(false); } } }); mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> { - if (mExpandedBubble != null && mExpandedBubble.getBubbleBarExpandedView() != null) { + BubbleBarExpandedView bbev = getExpandedView(); + if (bbev != null) { float alpha = (float) valueAnimator.getAnimatedValue(); - mExpandedBubble.getBubbleBarExpandedView().setTaskViewAlpha(alpha); - mExpandedBubble.getBubbleBarExpandedView().setAlpha(alpha); + bbev.setTaskViewAlpha(alpha); + bbev.setAlpha(alpha); } }); } @@ -116,11 +119,8 @@ public class BubbleBarAnimationHelper { public void animateExpansion(BubbleViewProvider expandedBubble, @Nullable Runnable afterAnimation) { mExpandedBubble = expandedBubble; - if (mExpandedBubble == null) { - return; - } - BubbleBarExpandedView bev = mExpandedBubble.getBubbleBarExpandedView(); - if (bev == null) { + final BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { return; } mIsExpanded = true; @@ -129,11 +129,11 @@ public class BubbleBarAnimationHelper { mExpandedViewContainerMatrix.setScaleY(0f); updateExpandedView(); - bev.setAnimating(true); - bev.setContentVisibility(false); - bev.setAlpha(0f); - bev.setTaskViewAlpha(0f); - bev.setVisibility(VISIBLE); + bbev.setAnimating(true); + bbev.setContentVisibility(false); + bbev.setAlpha(0f); + bbev.setTaskViewAlpha(0f); + bbev.setVisibility(VISIBLE); // Set the pivot point for the scale, so the view animates out from the bubble bar. Point bubbleBarPosition = mPositioner.getBubbleBarPosition(); @@ -143,7 +143,7 @@ public class BubbleBarAnimationHelper { bubbleBarPosition.x, bubbleBarPosition.y); - bev.setAnimationMatrix(mExpandedViewContainerMatrix); + bbev.setAnimationMatrix(mExpandedViewContainerMatrix); mExpandedViewAlphaAnimator.start(); @@ -156,13 +156,12 @@ public class BubbleBarAnimationHelper { AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), mScaleInSpringConfig) .addUpdateListener((target, values) -> { - mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix( - mExpandedViewContainerMatrix); + bbev.setAnimationMatrix(mExpandedViewContainerMatrix); }) .withEndActions(() -> { - bev.setAnimationMatrix(null); + bbev.setAnimationMatrix(null); updateExpandedView(); - bev.setSurfaceZOrderedOnTop(false); + bbev.setSurfaceZOrderedOnTop(false); if (afterAnimation != null) { afterAnimation.run(); } @@ -177,7 +176,8 @@ public class BubbleBarAnimationHelper { */ public void animateCollapse(Runnable endRunnable) { mIsExpanded = false; - if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) { + final BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { Log.w(TAG, "Trying to animate collapse without a bubble"); return; } @@ -196,17 +196,10 @@ public class BubbleBarAnimationHelper { EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), mScaleOutSpringConfig) .addUpdateListener((target, values) -> { - if (mExpandedBubble != null - && mExpandedBubble.getBubbleBarExpandedView() != null) { - mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix( - mExpandedViewContainerMatrix); - } + bbev.setAnimationMatrix(mExpandedViewContainerMatrix); }) .withEndActions(() -> { - if (mExpandedBubble != null - && mExpandedBubble.getBubbleBarExpandedView() != null) { - mExpandedBubble.getBubbleBarExpandedView().setAnimationMatrix(null); - } + bbev.setAnimationMatrix(null); if (endRunnable != null) { endRunnable.run(); } @@ -223,12 +216,20 @@ public class BubbleBarAnimationHelper { mExpandedViewAlphaAnimator.cancel(); } + private @Nullable BubbleBarExpandedView getExpandedView() { + BubbleViewProvider bubble = mExpandedBubble; + if (bubble != null) { + return bubble.getBubbleBarExpandedView(); + } + return null; + } + private void updateExpandedView() { - if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) { + BubbleBarExpandedView bbev = getExpandedView(); + if (bbev == null) { Log.w(TAG, "Trying to update the expanded view without a bubble"); return; } - BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView(); boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); final int padding = mPositioner.getBubbleBarExpandedViewPadding(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java index ef763ec45994..afd3b14e8b1d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.content.Context; +import android.content.Intent; import android.graphics.Rect; import android.os.SystemClock; import android.view.LayoutInflater; @@ -227,9 +228,12 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract } private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) { + final Intent intent = taskInfo.baseIntent; return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled) + && Intent.ACTION_MAIN.equals(intent.getAction()) + && intent.hasCategory(Intent.CATEGORY_LAUNCHER) && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS index deb7c6db338f..1385f42bc676 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS @@ -1,3 +1,7 @@ # WM shell sub-module desktop owners atsjenk@google.com +jorgegil@google.com madym@google.com +nmusgrave@google.com +pbdr@google.com +tkachenkoi@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS index a3803ed82844..8a0eea0a9bdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS @@ -2,3 +2,6 @@ atsjenk@google.com jorgegil@google.com madym@google.com +nmusgrave@google.com +pbdr@google.com +tkachenkoi@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl index 644a6a5114a7..7f4a8f1d476a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl @@ -16,6 +16,7 @@ package com.android.wm.shell.transition; +import android.view.SurfaceControl; import android.window.RemoteTransition; import android.window.TransitionFilter; @@ -42,6 +43,13 @@ interface IShellTransitions { */ IBinder getShellApplyToken() = 3; - /** Set listener that will receive callbacks about transitions involving home activity */ + /** + * Set listener that will receive callbacks about transitions involving home activity. + */ oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4; + + /** + * Returns a container surface for the home root task. + */ + SurfaceControl getHomeTaskOverlayContainer() = 5; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index b98762d5e104..af69b5272ad5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -64,7 +64,6 @@ import android.window.TransitionInfo; import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import android.window.WindowOrganizer; import androidx.annotation.BinderThread; @@ -72,6 +71,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; @@ -172,7 +172,7 @@ public class Transitions implements RemoteCallable<Transitions>, /** Transition to animate task to desktop. */ public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15; - private final WindowOrganizer mOrganizer; + private final ShellTaskOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; @@ -264,7 +264,7 @@ public class Transitions implements RemoteCallable<Transitions>, public Transitions(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, - @NonNull WindowOrganizer organizer, + @NonNull ShellTaskOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull ShellExecutor mainExecutor, @@ -280,7 +280,7 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellInit shellInit, @Nullable ShellCommandHandler shellCommandHandler, @NonNull ShellController shellController, - @NonNull WindowOrganizer organizer, + @NonNull ShellTaskOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull ShellExecutor mainExecutor, @@ -1240,6 +1240,10 @@ public class Transitions implements RemoteCallable<Transitions>, } } + private SurfaceControl getHomeTaskOverlayContainer() { + return mOrganizer.getHomeTaskOverlayContainer(); + } + /** * Interface for a callback that must be called after a TransitionHandler finishes playing an * animation. @@ -1470,6 +1474,17 @@ public class Transitions implements RemoteCallable<Transitions>, listener); }); } + + @Override + public SurfaceControl getHomeTaskOverlayContainer() { + SurfaceControl[] result = new SurfaceControl[1]; + executeRemoteCallWithTaskPermission(mTransitions, "getHomeTaskOverlayContainer", + (controller) -> { + result[0] = controller.getHomeTaskOverlayContainer(); + }, true /* blocking */); + // Return a copy as writing to parcel releases the original surface + return new SurfaceControl(result[0], "Transitions.HomeOverlay"); + } } private class SettingsObserver extends ContentObserver { diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index deebad545c5e..d718e157afdb 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -9,3 +9,6 @@ hwwang@google.com chenghsiuchang@google.com atsjenk@google.com jorgegil@google.com +nmusgrave@google.com +pbdr@google.com +tkachenkoi@google.com diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java index 065293960da7..9fe2cb11e804 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -16,6 +16,9 @@ package com.android.wm.shell.compatui; +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.CATEGORY_LAUNCHER; +import static android.hardware.usb.UsbManager.ACTION_USB_STATE; import static android.view.WindowInsets.Type.navigationBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -33,6 +36,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.ComponentName; +import android.content.Intent; import android.content.res.Configuration; import android.graphics.Rect; import android.testing.AndroidTestingRunner; @@ -108,7 +112,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mExecutor = new TestShellExecutor(); mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - false, /* topActivityBoundsLetterboxed */ true); + false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0, @@ -179,7 +183,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { // No diff clearInvocations(mWindowManager); TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - true, /* topActivityBoundsLetterboxed */ true); + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true)); verify(mWindowManager, never()).updateSurfacePosition(); @@ -200,7 +204,24 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { clearInvocations(mWindowManager); clearInvocations(mLayout); taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - false, /* topActivityBoundsLetterboxed */ true); + false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); + assertFalse( + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); + verify(mWindowManager).release(); + + // Recreate button + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); + assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); + + verify(mWindowManager).release(); + verify(mWindowManager).createLayout(/* canShow= */ true); + + // Change has no launcher category and is not main intent, dispose the component + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true, ACTION_USB_STATE, ""); assertFalse( mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); verify(mWindowManager).release(); @@ -217,7 +238,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { // inflated clearInvocations(mWindowManager); TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - false, /* topActivityBoundsLetterboxed */ true); + false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); verify(mWindowManager, never()).inflateLayout(); @@ -225,7 +246,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated. clearInvocations(mWindowManager); taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - true, /* topActivityBoundsLetterboxed */ true); + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); verify(mWindowManager).inflateLayout(); @@ -304,7 +325,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { clearInvocations(mWindowManager); spyOn(mWindowManager); TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ - true, /* topActivityBoundsLetterboxed */ true); + true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER); // User aspect ratio settings button has not yet been shown. doReturn(false).when(mUserAspectRatioButtonShownChecker).get(); @@ -378,7 +399,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { } private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton, - boolean topActivityBoundsLetterboxed) { + boolean topActivityBoundsLetterboxed, String action, String category) { ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); taskInfo.taskId = TASK_ID; taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton = @@ -386,6 +407,7 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed; taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK; taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity"); + taskInfo.baseIntent = new Intent(action).addCategory(category); return taskInfo; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java index 50802c3759c9..66efa02de764 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java @@ -40,12 +40,12 @@ import android.os.RemoteException; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionInfo.TransitionMode; -import android.window.WindowOrganizer; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; @@ -68,7 +68,7 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class HomeTransitionObserverTest extends ShellTestCase { - private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class); + private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class); private final TransactionPool mTransactionPool = mock(TransactionPool.class); private final Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 01c9bd0cb9f7..e22bf3de30e4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -87,7 +87,6 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import android.window.WindowOrganizer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -98,6 +97,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; @@ -130,7 +130,7 @@ import java.util.function.Function; @RunWith(AndroidJUnit4.class) public class ShellTransitionTests extends ShellTestCase { - private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class); + private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class); private final TransactionPool mTransactionPool = mock(TransactionPool.class); private final Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 79a735786c38..47411701e5ab 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -79,14 +79,6 @@ cc_defaults { "external/skia/src/core", ], - product_variables: { - eng: { - lto: { - never: true, - }, - }, - }, - target: { android: { include_dirs: [ diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 8445032293dd..69718a6c4b3e 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -43,12 +43,15 @@ cc_test { }, shared_libs: [ "libandroid_runtime", + "libbase", + "libinput", "libinputservice", "libhwui", "libgui", "libutils", ], static_libs: [ + "libflagtest", "libgmock", "libgtest", ], diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index d9efd3c2fd83..adfa91e96ebb 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <com_android_input_flags.h> +#include <flag_macros.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <input/PointerController.h> @@ -28,6 +30,8 @@ namespace android { +namespace input_flags = com::android::input::flags; + enum TestCursorType { CURSOR_TYPE_DEFAULT = 0, CURSOR_TYPE_HOVER, @@ -261,7 +265,20 @@ TEST_F(PointerControllerTest, useStylusTypeForStylusHover) { mPointerController->reloadPointerResources(); } -TEST_F(PointerControllerTest, updatePointerIcon) { +TEST_F_WITH_FLAGS(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) { + // Setting the presentation mode before a display viewport is set will not load any resources. + mPointerController->setPresentation(PointerController::Presentation::POINTER); + ASSERT_TRUE(mPolicy->noResourcesAreLoaded()); + + // When the display is set, then the resources are loaded. + ensureDisplayViewportIsSet(); + ASSERT_TRUE(mPolicy->allResourcesAreLoaded()); +} + +TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIcon, + REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(input_flags, + enable_pointer_choreographer))) { ensureDisplayViewportIsSet(); mPointerController->setPresentation(PointerController::Presentation::POINTER); mPointerController->unfade(PointerController::Transition::IMMEDIATE); @@ -277,6 +294,24 @@ TEST_F(PointerControllerTest, updatePointerIcon) { mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type)); } +TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIconWithChoreographer, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) { + // When PointerChoreographer is enabled, the presentation mode is set before the viewport. + mPointerController->setPresentation(PointerController::Presentation::POINTER); + ensureDisplayViewportIsSet(); + mPointerController->unfade(PointerController::Transition::IMMEDIATE); + + int32_t type = CURSOR_TYPE_ADDITIONAL; + std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type); + EXPECT_CALL(*mPointerSprite, setVisible(true)); + EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); + EXPECT_CALL(*mPointerSprite, + setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)), + Field(&SpriteIcon::hotSpotX, hotspot.first), + Field(&SpriteIcon::hotSpotY, hotspot.second)))); + mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type)); +} + TEST_F(PointerControllerTest, setCustomPointerIcon) { ensureDisplayViewportIsSet(); mPointerController->unfade(PointerController::Transition::IMMEDIATE); diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index 281eba66123b..6019aa8560e1 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -156,7 +156,7 @@ <string name="permission_storage">Photos and media</string> <!-- Notification permission will be granted of corresponding profile [CHAR LIMIT=30] --> - <string name="permission_notification">Notifications</string> + <string name="permission_notifications">Notifications</string> <!-- Apps permission will be granted of corresponding profile [CHAR LIMIT=30] --> <string name="permission_app_streaming">Apps</string> @@ -165,28 +165,31 @@ <string name="permission_nearby_device_streaming">Streaming</string> <!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_phone_summary">Can make and manage phone calls</string> + <string name="permission_phone_summary">Make and manage phone calls</string> <!-- Description of Call logs permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_call_logs_summary">Can read and write phone call log</string> + <string name="permission_call_logs_summary">Read and write phone call log</string> <!-- Description of SMS permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_sms_summary">Can send and view SMS messages</string> + <string name="permission_sms_summary">Send and view SMS messages</string> <!-- Description of contacts permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_contacts_summary">Can access your contacts</string> + <string name="permission_contacts_summary">Access your contacts</string> <!-- Description of calendar permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_calendar_summary">Can access your calendar</string> + <string name="permission_calendar_summary">Access your calendar</string> <!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_microphone_summary">Can record audio</string> + <string name="permission_microphone_summary">Record audio</string> <!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_nearby_devices_summary">Can find, connect to, and determine the relative position of nearby devices</string> + <string name="permission_nearby_devices_summary">Find, connect to, and determine the relative position of nearby devices</string> - <!-- Description of notification permission of corresponding profile [CHAR LIMIT=NONE] --> - <string name="permission_notification_summary">Can read all notifications, including information like contacts, messages, and photos</string> + <!-- Description of NLA (notification listener access) of corresponding profile [CHAR LIMIT=NONE] --> + <string name="permission_notification_listener_access_summary">Read all notifications, including information like contacts, messages, and photos</string> + + <!-- Description of NLA & POST_NOTIFICATION of corresponding profile [CHAR LIMIT=NONE] --> + <string name="permission_notifications_summary">\u2022 Read all notifications, including info like contacts, messages, and photos<br/>\u2022 Send notifications<br/><br/>You can manage this app\'s ability to read and send notifications anytime in Settings > Notifications.</string> <!-- Description of app streaming permission of corresponding profile [CHAR LIMIT=NONE] --> <string name="permission_app_streaming_summary">Stream your phone\u2019s apps</string> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 97016f5384f6..0abf285bd19c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -27,13 +27,13 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState; import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT; -import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES; -import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME; -import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON; -import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_PERMISSIONS; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_NAMES; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICONS; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_SUMMARIES; import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES; import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES; -import static com.android.companiondevicemanager.CompanionDeviceResources.TITLES; +import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES; import static com.android.companiondevicemanager.Utils.getApplicationLabel; import static com.android.companiondevicemanager.Utils.getHtmlFromResources; import static com.android.companiondevicemanager.Utils.getIcon; @@ -482,7 +482,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements return; } - title = getHtmlFromResources(this, TITLES.get(deviceProfile), deviceName); + title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName); setupPermissionList(deviceProfile); // Summary is not needed for selfManaged dialog. @@ -525,7 +525,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements mSelectedDevice = requireNonNull(deviceFilterPairs.get(0)); - final Drawable profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile)); + final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile)); updatePermissionUi(); @@ -545,14 +545,14 @@ public class CompanionDeviceActivity extends FragmentActivity implements throw new RuntimeException("Unsupported profile " + deviceProfile); } - profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile)); + profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile)); if (deviceProfile == null) { title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel); mButtonNotAllowMultipleDevices.setText(R.string.consent_no); } else { title = getHtmlFromResources(this, - R.string.chooser_title, getString(PROFILES_NAME.get(deviceProfile))); + R.string.chooser_title, getString(PROFILE_NAMES.get(deviceProfile))); } mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked); @@ -609,10 +609,10 @@ public class CompanionDeviceActivity extends FragmentActivity implements private void updatePermissionUi() { final String deviceProfile = mRequest.getDeviceProfile(); - final int summaryResourceId = SUMMARIES.get(deviceProfile); + final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile); final String remoteDeviceName = mSelectedDevice.getDisplayName(); final Spanned title = getHtmlFromResources( - this, TITLES.get(deviceProfile), mAppLabel, remoteDeviceName); + this, PROFILE_TITLES.get(deviceProfile), mAppLabel, remoteDeviceName); final Spanned summary; // No need to show permission consent dialog if it is a isSkipPrompt(true) @@ -680,7 +680,8 @@ public class CompanionDeviceActivity extends FragmentActivity implements // and when mPermissionListRecyclerView is fully populated. // Lastly, disable the Allow and Don't allow buttons. private void setupPermissionList(String deviceProfile) { - final List<Integer> permissionTypes = new ArrayList<>(PERMISSION_TYPES.get(deviceProfile)); + final List<Integer> permissionTypes = new ArrayList<>( + PROFILE_PERMISSIONS.get(deviceProfile)); mPermissionListAdapter.setPermissionType(permissionTypes); mPermissionListRecyclerView.setAdapter(mPermissionListAdapter); mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java index 551e9754032b..23a11d618085 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java @@ -22,28 +22,15 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER; import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES; import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING; import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; - -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALL_LOGS; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CHANGE_MEDIA_OUTPUT; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICE_STREAMING; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_PHONE; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_SMS; -import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_STORAGE; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; +import android.os.Build; import android.util.ArrayMap; import android.util.ArraySet; -import com.android.media.flags.Flags; - import java.util.Arrays; import java.util.List; import java.util.Map; @@ -54,7 +41,85 @@ import java.util.Set; * for the corresponding profile. */ final class CompanionDeviceResources { - static final Map<String, Integer> TITLES; + + // Permission resources + private static final int PERMISSION_NOTIFICATION_LISTENER_ACCESS = 0; + private static final int PERMISSION_STORAGE = 1; + private static final int PERMISSION_APP_STREAMING = 2; + private static final int PERMISSION_PHONE = 3; + private static final int PERMISSION_SMS = 4; + private static final int PERMISSION_CONTACTS = 5; + private static final int PERMISSION_CALENDAR = 6; + private static final int PERMISSION_NEARBY_DEVICES = 7; + private static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8; + private static final int PERMISSION_MICROPHONE = 9; + private static final int PERMISSION_CALL_LOGS = 10; + // Notification Listener Access & POST_NOTIFICATION permission + private static final int PERMISSION_NOTIFICATIONS = 11; + private static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 12; + + static final Map<Integer, Integer> PERMISSION_TITLES; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.string.permission_notifications); + map.put(PERMISSION_STORAGE, R.string.permission_storage); + map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming); + map.put(PERMISSION_PHONE, R.string.permission_phone); + map.put(PERMISSION_SMS, R.string.permission_sms); + map.put(PERMISSION_CONTACTS, R.string.permission_contacts); + map.put(PERMISSION_CALENDAR, R.string.permission_calendar); + map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices); + map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming); + map.put(PERMISSION_MICROPHONE, R.string.permission_microphone); + map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs); + map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications); + map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control); + PERMISSION_TITLES = unmodifiableMap(map); + } + + static final Map<Integer, Integer> PERMISSION_SUMMARIES; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, + R.string.permission_notification_listener_access_summary); + map.put(PERMISSION_STORAGE, R.string.permission_storage_summary); + map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary); + map.put(PERMISSION_PHONE, R.string.permission_phone_summary); + map.put(PERMISSION_SMS, R.string.permission_sms_summary); + map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary); + map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary); + map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary); + map.put(PERMISSION_NEARBY_DEVICE_STREAMING, + R.string.permission_nearby_device_streaming_summary); + map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary); + map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary); + map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications_summary); + map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary); + PERMISSION_SUMMARIES = unmodifiableMap(map); + } + + static final Map<Integer, Integer> PERMISSION_ICONS; + static { + final Map<Integer, Integer> map = new ArrayMap<>(); + map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.drawable.ic_permission_notifications); + map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage); + map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming); + map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone); + map.put(PERMISSION_SMS, R.drawable.ic_permission_sms); + map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts); + map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar); + map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices); + map.put(PERMISSION_NEARBY_DEVICE_STREAMING, + R.drawable.ic_permission_nearby_device_streaming); + map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone); + map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs); + map.put(PERMISSION_NOTIFICATIONS, R.drawable.ic_permission_notifications); + map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control); + PERMISSION_ICONS = unmodifiableMap(map); + } + + // Profile resources + static final Map<String, Integer> PROFILE_TITLES; static { final Map<String, Integer> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming); @@ -65,71 +130,61 @@ final class CompanionDeviceResources { map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses); map.put(null, R.string.confirmation_title); - TITLES = unmodifiableMap(map); + PROFILE_TITLES = unmodifiableMap(map); + } + + static final Map<String, Integer> PROFILE_SUMMARIES; + static { + final Map<String, Integer> map = new ArrayMap<>(); + map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch); + map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses); + map.put(null, R.string.summary_generic); + + PROFILE_SUMMARIES = unmodifiableMap(map); } - static final Map<String, List<Integer>> PERMISSION_TYPES; + static final Map<String, List<Integer>> PROFILE_PERMISSIONS; static { final Map<String, List<Integer>> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING)); map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList( - PERMISSION_NOTIFICATION, PERMISSION_STORAGE)); + PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE)); map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING)); - if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) { - map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE, - PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR, - PERMISSION_NEARBY_DEVICES)); - } else { - map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE, + if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) { + map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE, PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES, PERMISSION_CHANGE_MEDIA_OUTPUT)); + } else { + map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION_LISTENER_ACCESS, + PERMISSION_PHONE, PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, + PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES)); } - map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE, - PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE, + map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION_LISTENER_ACCESS, + PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE, PERMISSION_NEARBY_DEVICES)); - PERMISSION_TYPES = unmodifiableMap(map); + PROFILE_PERMISSIONS = unmodifiableMap(map); } - static final Map<String, Integer> SUMMARIES; - static { - final Map<String, Integer> map = new ArrayMap<>(); - map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch); - map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses); - map.put(null, R.string.summary_generic); - - SUMMARIES = unmodifiableMap(map); - } - - static final Map<String, Integer> PROFILES_NAME; + static final Map<String, Integer> PROFILE_NAMES; static { final Map<String, Integer> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch); map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_glasses); map.put(null, R.string.profile_name_generic); - PROFILES_NAME = unmodifiableMap(map); - } - - static final Map<String, Integer> PROFILES_NAME_MULTI; - static { - final Map<String, Integer> map = new ArrayMap<>(); - map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_generic); - map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch); - map.put(null, R.string.profile_name_generic); - - PROFILES_NAME_MULTI = unmodifiableMap(map); + PROFILE_NAMES = unmodifiableMap(map); } - static final Map<String, Integer> PROFILE_ICON; + static final Map<String, Integer> PROFILE_ICONS; static { final Map<String, Integer> map = new ArrayMap<>(); map.put(DEVICE_PROFILE_WATCH, R.drawable.ic_watch); map.put(DEVICE_PROFILE_GLASSES, R.drawable.ic_glasses); map.put(null, R.drawable.ic_device_other); - PROFILE_ICON = unmodifiableMap(map); + PROFILE_ICONS = unmodifiableMap(map); } static final Set<String> SUPPORTED_PROFILES; diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java index e21aee3cedb8..4a1f8014a2f0 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java @@ -16,14 +16,14 @@ package com.android.companiondevicemanager; +import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_ICONS; +import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_SUMMARIES; +import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TITLES; import static com.android.companiondevicemanager.Utils.getHtmlFromResources; import static com.android.companiondevicemanager.Utils.getIcon; -import static java.util.Collections.unmodifiableMap; - import android.content.Context; import android.text.Spanned; -import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -35,7 +35,6 @@ import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import java.util.List; -import java.util.Map; class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.ViewHolder> { private final Context mContext; @@ -43,75 +42,6 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V // Add the expand buttons if permissions are more than PERMISSION_SIZE in the permission list. private static final int PERMISSION_SIZE = 2; - static final int PERMISSION_NOTIFICATION = 0; - static final int PERMISSION_STORAGE = 1; - static final int PERMISSION_APP_STREAMING = 2; - static final int PERMISSION_PHONE = 3; - static final int PERMISSION_SMS = 4; - static final int PERMISSION_CONTACTS = 5; - static final int PERMISSION_CALENDAR = 6; - static final int PERMISSION_NEARBY_DEVICES = 7; - static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8; - static final int PERMISSION_MICROPHONE = 9; - static final int PERMISSION_CALL_LOGS = 10; - static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 11; - - private static final Map<Integer, Integer> sTitleMap; - static { - final Map<Integer, Integer> map = new ArrayMap<>(); - map.put(PERMISSION_NOTIFICATION, R.string.permission_notification); - map.put(PERMISSION_STORAGE, R.string.permission_storage); - map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming); - map.put(PERMISSION_PHONE, R.string.permission_phone); - map.put(PERMISSION_SMS, R.string.permission_sms); - map.put(PERMISSION_CONTACTS, R.string.permission_contacts); - map.put(PERMISSION_CALENDAR, R.string.permission_calendar); - map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices); - map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming); - map.put(PERMISSION_MICROPHONE, R.string.permission_microphone); - map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs); - map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control); - sTitleMap = unmodifiableMap(map); - } - - private static final Map<Integer, Integer> sSummaryMap; - static { - final Map<Integer, Integer> map = new ArrayMap<>(); - map.put(PERMISSION_NOTIFICATION, R.string.permission_notification_summary); - map.put(PERMISSION_STORAGE, R.string.permission_storage_summary); - map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary); - map.put(PERMISSION_PHONE, R.string.permission_phone_summary); - map.put(PERMISSION_SMS, R.string.permission_sms_summary); - map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary); - map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary); - map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary); - map.put(PERMISSION_NEARBY_DEVICE_STREAMING, - R.string.permission_nearby_device_streaming_summary); - map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary); - map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary); - map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary); - sSummaryMap = unmodifiableMap(map); - } - - private static final Map<Integer, Integer> sIconMap; - static { - final Map<Integer, Integer> map = new ArrayMap<>(); - map.put(PERMISSION_NOTIFICATION, R.drawable.ic_permission_notifications); - map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage); - map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming); - map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone); - map.put(PERMISSION_SMS, R.drawable.ic_permission_sms); - map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts); - map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar); - map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices); - map.put(PERMISSION_NEARBY_DEVICE_STREAMING, - R.drawable.ic_permission_nearby_device_streaming); - map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone); - map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs); - map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control); - sIconMap = unmodifiableMap(map); - } - PermissionListAdapter(Context context) { mContext = context; } @@ -121,7 +51,8 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V View view = LayoutInflater.from(parent.getContext()).inflate( R.layout.list_item_permission, parent, false); ViewHolder viewHolder = new ViewHolder(view); - viewHolder.mPermissionIcon.setImageDrawable(getIcon(mContext, sIconMap.get(viewType))); + viewHolder.mPermissionIcon.setImageDrawable( + getIcon(mContext, PERMISSION_ICONS.get(viewType))); if (viewHolder.mExpandButton.getTag() == null) { viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more); @@ -165,8 +96,8 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V @Override public void onBindViewHolder(ViewHolder holder, int position) { int type = getItemViewType(position); - final Spanned title = getHtmlFromResources(mContext, sTitleMap.get(type)); - final Spanned summary = getHtmlFromResources(mContext, sSummaryMap.get(type)); + final Spanned title = getHtmlFromResources(mContext, PERMISSION_TITLES.get(type)); + final Spanned summary = getHtmlFromResources(mContext, PERMISSION_SUMMARIES.get(type)); holder.mPermissionSummary.setText(summary); holder.mPermissionName.setText(title); @@ -192,6 +123,7 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V private final TextView mPermissionSummary; private final ImageView mPermissionIcon; private final ImageButton mExpandButton; + ViewHolder(View itemView) { super(itemView); mPermissionName = itemView.findViewById(R.id.permission_name); @@ -203,7 +135,7 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V private void setAccessibility(View view, int viewType, int action, int statusResourceId, int actionResourceId) { - final String permission = mContext.getString(sTitleMap.get(viewType)); + final String permission = mContext.getString(PERMISSION_TITLES.get(viewType)); if (actionResourceId != 0) { view.announceForAccessibility( diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java index 96a11eeb3b78..5b39f4ee1541 100644 --- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java +++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java @@ -112,26 +112,6 @@ public class RestrictedLockUtils { } /** - * Shows restricted setting dialog. - */ - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - public static void sendShowRestrictedSettingDialogIntent(Context context, - String packageName, int uid) { - final Intent intent = getShowRestrictedSettingsIntent(packageName, uid); - context.startActivity(intent); - } - - /** - * Gets restricted settings dialog intent. - */ - private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) { - final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); - intent.putExtra(Intent.EXTRA_UID, uid); - return intent; - } - - /** * Checks if current user is profile or not */ @RequiresApi(Build.VERSION_CODES.M) @@ -238,4 +218,35 @@ public class RestrictedLockUtils { + '}'; } } + + + /** + * Shows restricted setting dialog. + * + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + public static void sendShowRestrictedSettingDialogIntent(Context context, + String packageName, int uid) { + final Intent intent = getShowRestrictedSettingsIntent(packageName, uid); + context.startActivity(intent); + } + + /** + * Gets restricted settings dialog intent. + * + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated + private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) { + final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(Intent.EXTRA_UID, uid); + return intent; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 4454b710b7e4..02374462f093 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -77,6 +77,9 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS); ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN); } + + ECM_KEYS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS); + ECM_KEYS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java index db2a6ec2da68..50e3bd08026c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java @@ -96,12 +96,29 @@ public class RestrictedPreference extends TwoTargetPreference { mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); } + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param restriction The key identifying the setting + * @param packageName the package to check the restriction for + * @param uid the uid of the package + */ + public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) { + mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid); + } + @Override public void setEnabled(boolean enabled) { if (enabled && isDisabledByAdmin()) { mHelper.setDisabledByAdmin(null); return; } + + if (enabled && isDisabledByEcm()) { + mHelper.setDisabledByEcm(null); + return; + } + super.setEnabled(enabled); } @@ -111,16 +128,14 @@ public class RestrictedPreference extends TwoTargetPreference { } } - public void setDisabledByAppOps(boolean disabled) { - if (mHelper.setDisabledByAppOps(disabled)) { - notifyChanged(); - } - } - public boolean isDisabledByAdmin() { return mHelper.isDisabledByAdmin(); } + public boolean isDisabledByEcm() { + return mHelper.isDisabledByEcm(); + } + public int getUid() { return mHelper != null ? mHelper.uid : Process.INVALID_UID; } @@ -128,4 +143,16 @@ public class RestrictedPreference extends TwoTargetPreference { public String getPackageName() { return mHelper != null ? mHelper.packageName : null; } + + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated + public void setDisabledByAppOps(boolean disabled) { + if (mHelper.setDisabledByAppOps(disabled)) { + notifyChanged(); + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 29ea25e13835..a479269f40fb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -17,10 +17,12 @@ package com.android.settingslib; import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY; + import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.Intent; import android.content.res.TypedArray; import android.os.Build; import android.os.UserHandle; @@ -52,7 +54,8 @@ public class RestrictedPreferenceHelper { private String mAttrUserRestriction = null; private boolean mDisabledSummary = false; - private boolean mDisabledByAppOps; + private boolean mDisabledByEcm; + private Intent mDisabledByEcmIntent = null; public RestrictedPreferenceHelper(Context context, Preference preference, AttributeSet attrs, String packageName, int uid) { @@ -101,7 +104,7 @@ public class RestrictedPreferenceHelper { * Modify PreferenceViewHolder to add padlock if restriction is disabled. */ public void onBindViewHolder(PreferenceViewHolder holder) { - if (mDisabledByAdmin || mDisabledByAppOps) { + if (mDisabledByAdmin || mDisabledByEcm) { holder.itemView.setEnabled(true); } if (mDisabledSummary) { @@ -112,7 +115,7 @@ public class RestrictedPreferenceHelper { : mContext.getString(R.string.disabled_by_admin_summary_text); if (mDisabledByAdmin) { summaryView.setText(disabledText); - } else if (mDisabledByAppOps) { + } else if (mDisabledByEcm) { summaryView.setText(R.string.disabled_by_app_ops_text); } else if (TextUtils.equals(disabledText, summaryView.getText())) { // It's previously set to disabled text, clear it. @@ -144,7 +147,12 @@ public class RestrictedPreferenceHelper { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin); return true; } - if (mDisabledByAppOps) { + if (mDisabledByEcm) { + if (android.security.Flags.extendEcmToAllSettings()) { + mContext.startActivity(mDisabledByEcmIntent); + return true; + } + RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName, uid); return true; @@ -174,6 +182,20 @@ public class RestrictedPreferenceHelper { } /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param restriction The key identifying the setting + * @param packageName the package to check the restriction for + * @param uid the uid of the package + */ + public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) { + updatePackageDetails(packageName, uid); + Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation( + mContext, restriction, uid, packageName); + setDisabledByEcm(intent); + } + + /** * @return EnforcedAdmin if we have been passed the restriction in the xml. */ public EnforcedAdmin checkRestrictionEnforced() { @@ -211,10 +233,19 @@ public class RestrictedPreferenceHelper { return changed; } - public boolean setDisabledByAppOps(boolean disabled) { + /** + * Disable the preference based on the passed in Intent + * @param disabledIntent The intent which is started when the user clicks the disabled + * preference. If it is {@code null}, then this preference will be enabled. Otherwise, it will + * be disabled. + * @return true if the disabled state was changed. + */ + public boolean setDisabledByEcm(Intent disabledIntent) { + boolean disabled = disabledIntent != null; boolean changed = false; - if (mDisabledByAppOps != disabled) { - mDisabledByAppOps = disabled; + if (mDisabledByEcm != disabled) { + mDisabledByEcmIntent = disabledIntent; + mDisabledByEcm = disabled; changed = true; updateDisabledState(); } @@ -226,8 +257,8 @@ public class RestrictedPreferenceHelper { return mDisabledByAdmin; } - public boolean isDisabledByAppOps() { - return mDisabledByAppOps; + public boolean isDisabledByEcm() { + return mDisabledByEcm; } public void updatePackageDetails(String packageName, int uid) { @@ -236,13 +267,31 @@ public class RestrictedPreferenceHelper { } private void updateDisabledState() { + boolean isEnabled = !(mDisabledByAdmin || mDisabledByEcm); if (!(mPreference instanceof RestrictedTopLevelPreference)) { - mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); + mPreference.setEnabled(isEnabled); } if (mPreference instanceof PrimarySwitchPreference) { - ((PrimarySwitchPreference) mPreference) - .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); + ((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled); } } + + + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated + public boolean setDisabledByAppOps(boolean disabled) { + boolean changed = false; + if (mDisabledByEcm != disabled) { + mDisabledByEcm = disabled; + changed = true; + updateDisabledState(); + } + + return changed; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index 60321eb1a9dc..3b8f66577f6e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -197,6 +197,17 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); } + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param restriction The key identifying the setting + * @param packageName the package to check the restriction for + * @param uid the uid of the package + */ + public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) { + mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid); + } + @Override public void setEnabled(boolean enabled) { boolean changed = false; @@ -204,8 +215,8 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { mHelper.setDisabledByAdmin(null); changed = true; } - if (enabled && isDisabledByAppOps()) { - mHelper.setDisabledByAppOps(false); + if (enabled && isDisabledByEcm()) { + mHelper.setDisabledByEcm(null); changed = true; } if (!changed) { @@ -223,25 +234,50 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { return mHelper.isDisabledByAdmin(); } + public boolean isDisabledByEcm() { + return mHelper.isDisabledByEcm(); + } + + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated private void setDisabledByAppOps(boolean disabled) { if (mHelper.setDisabledByAppOps(disabled)) { notifyChanged(); } } - public boolean isDisabledByAppOps() { - return mHelper.isDisabledByAppOps(); - } - + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated public int getUid() { return mHelper != null ? mHelper.uid : Process.INVALID_UID; } + /** + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated public String getPackageName() { return mHelper != null ? mHelper.packageName : null; } - /** Updates enabled state based on associated package. */ + /** + * Updates enabled state based on associated package + * + * @deprecated TODO(b/308921175): This will be deleted with the + * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new + * code. + */ + @Deprecated public void updateState( @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) { mHelper.updatePackageDetails(packageName, uid); @@ -258,7 +294,7 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat { setEnabled(false); } else if (isEnabled) { setEnabled(true); - } else if (appOpsAllowed && isDisabledByAppOps()) { + } else if (appOpsAllowed && isDisabledByEcm()) { setEnabled(true); } else if (!appOpsAllowed){ setDisabledByAppOps(true); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 460d9f040e68..ec456e09a3c1 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -106,6 +106,9 @@ public class SecureSettings { Settings.Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED, Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, + // ACCESSIBILITY_QS_TARGETS needs to be restored after ENABLED_ACCESSIBILITY_SERVICES + // but before QS_TILES + Settings.Secure.ACCESSIBILITY_QS_TARGETS, Settings.Secure.QS_TILES, Settings.Secure.QS_AUTO_ADDED_TILES, Settings.Secure.CONTROLS_ENABLED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 6c48110ef10b..5ad14ceb31cf 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -320,6 +320,9 @@ public class SecureSettingsValidators { VALIDATORS.put( Secure.ACCESSIBILITY_BUTTON_TARGETS, ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR); + VALIDATORS.put( + Secure.ACCESSIBILITY_QS_TARGETS, + ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ONE_HANDED_MODE_ACTIVATED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ONE_HANDED_MODE_ENABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index a97888949446..5afcd5db1b5f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1813,6 +1813,9 @@ class SettingsProtoDumpUtil { Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, SecureSettingsProto.Accessibility.BUTTON_TARGETS); dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_QS_TARGETS, + SecureSettingsProto.Accessibility.QS_TARGETS); + dumpSetting(s, p, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY, SecureSettingsProto.Accessibility.ACCESSIBILITY_MAGNIFICATION_CAPABILITY); dumpSetting(s, p, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d12d9d665a8c..bacab0f8f1e8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -879,6 +879,9 @@ <uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" /> + <!-- Permissions required for CTS test - CtsAccessibilityServiceTestCases--> + <uses-permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7061e2cb8a4e..f10ac1bf1e0a 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -204,6 +204,7 @@ android_library { "lottie", "LowLightDreamLib", "motion_tool_lib", + "notification_flags_lib", ], libs: [ "keepanno-annotations", @@ -328,6 +329,7 @@ android_library { "androidx.compose.ui_ui", "flag-junit", "platform-test-annotations", + "notification_flags_lib", ], } 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 f7d9056c33dc..9c46ebdc5ac8 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -21,7 +21,6 @@ import android.app.ActivityTaskManager import android.app.PendingIntent import android.app.TaskInfo import android.graphics.Matrix -import android.graphics.Path import android.graphics.Rect import android.graphics.RectF import android.os.Build @@ -37,7 +36,6 @@ import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewGroup import android.view.WindowManager -import android.view.animation.Interpolator import android.view.animation.PathInterpolator import androidx.annotation.AnyThread import androidx.annotation.BinderThread @@ -93,7 +91,7 @@ class ActivityLaunchAnimator( val INTERPOLATORS = LaunchAnimator.Interpolators( positionInterpolator = Interpolators.EMPHASIZED, - positionXInterpolator = createPositionXInterpolator(), + positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT, contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN, contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f) ) @@ -121,16 +119,6 @@ class ActivityLaunchAnimator( * cancelled by WM. */ private const val LONG_LAUNCH_TIMEOUT = 5_000L - - private fun createPositionXInterpolator(): Interpolator { - val path = - Path().apply { - moveTo(0f, 0f) - cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f) - cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f) - } - return PathInterpolator(path) - } } /** diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 185a06c70f9e..d83f3aae1ace 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -14,6 +14,7 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -28,15 +29,20 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.FixedSizeEdgeDetector +import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.SceneTransitionLayoutState import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.transform object Communal { @@ -60,7 +66,7 @@ val sceneTransitions = transitions { * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture * handling and transitions before the full Flexiglass layout is ready. */ -@OptIn(ExperimentalComposeUiApi::class) +@OptIn(ExperimentalComposeUiApi::class, ExperimentalCoroutinesApi::class) @Composable fun CommunalContainer( modifier: Modifier = Modifier, @@ -81,6 +87,15 @@ fun CommunalContainer( return } + // This effect exposes the SceneTransitionLayout's observable transition state to the rest of + // the system, and unsets it when the view is disposed to avoid a memory leak. + DisposableEffect(viewModel, sceneTransitionLayoutState) { + viewModel.setTransitionState( + sceneTransitionLayoutState.observableTransitionState().map { it.toModel() } + ) + onDispose { viewModel.setTransitionState(null) } + } + Box(modifier = modifier.fillMaxSize()) { SceneTransitionLayout( modifier = Modifier.fillMaxSize(), @@ -171,18 +186,40 @@ private fun SceneScope.CommunalScene( Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) } } -// TODO(b/293899074): Remove these conversions once Compose can be used throughout SysUI. +// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI. object TransitionSceneKey { val Blank = CommunalSceneKey.Blank.toTransitionSceneKey() val Communal = CommunalSceneKey.Communal.toTransitionSceneKey() } +// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI. +fun SceneKey.toCommunalSceneKey(): CommunalSceneKey { + return this.identity as CommunalSceneKey +} + +// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI. fun CommunalSceneKey.toTransitionSceneKey(): SceneKey { return SceneKey(name = toString(), identity = this) } -fun SceneKey.toCommunalSceneKey(): CommunalSceneKey { - return this.identity as CommunalSceneKey +/** + * Converts between the [SceneTransitionLayout] state class and our forked data class that can be + * used throughout SysUI. + */ +// TODO(b/315490861): Remove these conversions once Compose can be used throughout SysUI. +fun ObservableTransitionState.toModel(): ObservableCommunalTransitionState { + return when (this) { + is ObservableTransitionState.Idle -> + ObservableCommunalTransitionState.Idle(scene.toCommunalSceneKey()) + is ObservableTransitionState.Transition -> + ObservableCommunalTransitionState.Transition( + fromScene = fromScene.toCommunalSceneKey(), + toScene = toScene.toCommunalSceneKey(), + progress = progress, + isInitiatedByUserInput = isInitiatedByUserInput, + isUserInputOngoing = isUserInputOngoing, + ) + } } object ContainerDimensions { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt index ded6cc155b0b..32025b4f1258 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt @@ -73,37 +73,37 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) { internal fun Modifier.nestedScrollToScene( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, ) = this then NestedScrollToSceneElement( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) private data class NestedScrollToSceneElement( private val layoutImpl: SceneTransitionLayoutImpl, private val orientation: Orientation, - private val startBehavior: NestedScrollBehavior, - private val endBehavior: NestedScrollBehavior, + private val topOrLeftBehavior: NestedScrollBehavior, + private val bottomOrRightBehavior: NestedScrollBehavior, ) : ModifierNodeElement<NestedScrollToSceneNode>() { override fun create() = NestedScrollToSceneNode( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) override fun update(node: NestedScrollToSceneNode) { node.update( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) } @@ -111,23 +111,23 @@ private data class NestedScrollToSceneElement( name = "nestedScrollToScene" properties["layoutImpl"] = layoutImpl properties["orientation"] = orientation - properties["startBehavior"] = startBehavior - properties["endBehavior"] = endBehavior + properties["topOrLeftBehavior"] = topOrLeftBehavior + properties["bottomOrRightBehavior"] = bottomOrRightBehavior } } private class NestedScrollToSceneNode( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, ) : DelegatingNode() { private var priorityNestedScrollConnection: PriorityNestedScrollConnection = scenePriorityNestedScrollConnection( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) private var nestedScrollNode: DelegatableNode = @@ -148,8 +148,8 @@ private class NestedScrollToSceneNode( fun update( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, ) { // Clean up the old nested scroll connection priorityNestedScrollConnection.reset() @@ -160,8 +160,8 @@ private class NestedScrollToSceneNode( scenePriorityNestedScrollConnection( layoutImpl = layoutImpl, orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) nestedScrollNode = nestedScrollModifierNode( @@ -175,12 +175,12 @@ private class NestedScrollToSceneNode( private fun scenePriorityNestedScrollConnection( layoutImpl: SceneTransitionLayoutImpl, orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + topOrLeftBehavior: NestedScrollBehavior, + bottomOrRightBehavior: NestedScrollBehavior, ) = SceneNestedScrollHandler( gestureHandler = layoutImpl.gestureHandler(orientation = orientation), - startBehavior = startBehavior, - endBehavior = endBehavior, + topOrLeftBehavior = topOrLeftBehavior, + bottomOrRightBehavior = bottomOrRightBehavior, ) .connection diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index f5561cb404b6..6a7a3a00d4fe 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -86,16 +86,26 @@ private class SceneScopeImpl( return element(layoutImpl, scene, key) } - override fun Modifier.nestedScrollToScene( - orientation: Orientation, - startBehavior: NestedScrollBehavior, - endBehavior: NestedScrollBehavior, + override fun Modifier.horizontalNestedScrollToScene( + leftBehavior: NestedScrollBehavior, + rightBehavior: NestedScrollBehavior, ): Modifier = nestedScrollToScene( layoutImpl = layoutImpl, - orientation = orientation, - startBehavior = startBehavior, - endBehavior = endBehavior, + orientation = Orientation.Horizontal, + topOrLeftBehavior = leftBehavior, + bottomOrRightBehavior = rightBehavior, + ) + + override fun Modifier.verticalNestedScrollToScene( + topBehavior: NestedScrollBehavior, + bottomBehavior: NestedScrollBehavior + ): Modifier = + nestedScrollToScene( + layoutImpl = layoutImpl, + orientation = Orientation.Vertical, + topOrLeftBehavior = topBehavior, + bottomOrRightBehavior = bottomBehavior, ) @Composable diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index 01179d3b8f73..03f37d0c9bda 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -494,9 +494,9 @@ private class SceneDraggableHandler( } internal class SceneNestedScrollHandler( - private val gestureHandler: SceneGestureHandler, - private val startBehavior: NestedScrollBehavior, - private val endBehavior: NestedScrollBehavior, + private val gestureHandler: SceneGestureHandler, + private val topOrLeftBehavior: NestedScrollBehavior, + private val bottomOrRightBehavior: NestedScrollBehavior, ) : NestedScrollHandler { override val connection: PriorityNestedScrollConnection = nestedScrollConnection() @@ -565,8 +565,8 @@ internal class SceneNestedScrollHandler( canStartPostScroll = { offsetAvailable, offsetBeforeStart -> val behavior: NestedScrollBehavior = when { - offsetAvailable > 0f -> startBehavior - offsetAvailable < 0f -> endBehavior + offsetAvailable > 0f -> topOrLeftBehavior + offsetAvailable < 0f -> bottomOrRightBehavior else -> return@PriorityNestedScrollConnection false } @@ -594,8 +594,8 @@ internal class SceneNestedScrollHandler( canStartPostFling = { velocityAvailable -> val behavior: NestedScrollBehavior = when { - velocityAvailable > 0f -> startBehavior - velocityAvailable < 0f -> endBehavior + velocityAvailable > 0f -> topOrLeftBehavior + velocityAvailable < 0f -> bottomOrRightBehavior else -> return@PriorityNestedScrollConnection false } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index afa184b15901..239971ff6be8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -126,14 +126,24 @@ interface SceneScope { * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable * component. * - * @param orientation is used to determine if we handle top/bottom or left/right events. - * @param startBehavior when we should perform the overscroll animation at the top/left. - * @param endBehavior when we should perform the overscroll animation at the bottom/right. + * @param leftBehavior when we should perform the overscroll animation at the left. + * @param rightBehavior when we should perform the overscroll animation at the right. */ - fun Modifier.nestedScrollToScene( - orientation: Orientation, - startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, - endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + fun Modifier.horizontalNestedScrollToScene( + leftBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + rightBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + ): Modifier + + /** + * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable + * component. + * + * @param topBehavior when we should perform the overscroll animation at the top. + * @param bottomBehavior when we should perform the overscroll animation at the bottom. + */ + fun Modifier.verticalNestedScrollToScene( + topBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, ): Modifier /** diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index aa942e039856..34afc4c91d4c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -117,8 +117,8 @@ class SceneGestureHandlerTest { fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) = SceneNestedScrollHandler( gestureHandler = sceneGestureHandler, - startBehavior = nestedScrollBehavior, - endBehavior = nestedScrollBehavior, + topOrLeftBehavior = nestedScrollBehavior, + bottomOrRightBehavior = nestedScrollBehavior, ) .connection diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index 7196de6608cb..65176e1c5c0d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -29,6 +30,8 @@ import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before @@ -40,29 +43,30 @@ import org.junit.runner.RunWith class CommunalRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: CommunalRepositoryImpl - private lateinit var testScope: TestScope + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic - private lateinit var sceneContainerFlags: FakeSceneContainerFlags private lateinit var sceneContainerRepository: SceneContainerRepository @Before fun setUp() { - testScope = TestScope() - val sceneTestUtils = SceneTestUtils(this) - sceneContainerFlags = FakeSceneContainerFlags(enabled = false) sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository() featureFlagsClassic = FakeFeatureFlagsClassic() featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) - underTest = - CommunalRepositoryImpl( - featureFlagsClassic, - sceneContainerFlags, - sceneContainerRepository, - ) + underTest = createRepositoryImpl(false) + } + + private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl { + return CommunalRepositoryImpl( + testScope.backgroundScope, + featureFlagsClassic, + FakeSceneContainerFlags(enabled = sceneContainerEnabled), + sceneContainerRepository, + ) } @Test @@ -86,13 +90,7 @@ class CommunalRepositoryImplTest : SysuiTestCase() { @Test fun isCommunalShowing_sceneContainerEnabled_onCommunalScene_true() = testScope.runTest { - sceneContainerFlags = FakeSceneContainerFlags(enabled = true) - underTest = - CommunalRepositoryImpl( - featureFlagsClassic, - sceneContainerFlags, - sceneContainerRepository, - ) + underTest = createRepositoryImpl(true) sceneContainerRepository.setDesiredScene(SceneModel(key = SceneKey.Communal)) @@ -103,17 +101,49 @@ class CommunalRepositoryImplTest : SysuiTestCase() { @Test fun isCommunalShowing_sceneContainerEnabled_onLockscreenScene_false() = testScope.runTest { - sceneContainerFlags = FakeSceneContainerFlags(enabled = true) - underTest = - CommunalRepositoryImpl( - featureFlagsClassic, - sceneContainerFlags, - sceneContainerRepository, - ) + underTest = createRepositoryImpl(true) sceneContainerRepository.setDesiredScene(SceneModel(key = SceneKey.Lockscreen)) val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing) assertThat(isCommunalHubShowing).isFalse() } + + @Test + fun transitionState_idleByDefault() = + testScope.runTest { + val transitionState by collectLastValue(underTest.transitionState) + assertThat(transitionState) + .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)) + } + + @Test + fun transitionState_setTransitionState_returnsNewValue() = + testScope.runTest { + val expectedSceneKey = CommunalSceneKey.Communal + underTest.setTransitionState( + flowOf(ObservableCommunalTransitionState.Idle(expectedSceneKey)) + ) + + val transitionState by collectLastValue(underTest.transitionState) + assertThat(transitionState) + .isEqualTo(ObservableCommunalTransitionState.Idle(expectedSceneKey)) + } + + @Test + fun transitionState_setNullTransitionState_returnsDefaultValue() = + testScope.runTest { + // Set a value for the transition state flow. + underTest.setTransitionState( + flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)) + ) + + // Set the transition state flow back to null. + underTest.setTransitionState(null) + + // Flow returns default scene key. + val transitionState by collectLastValue(underTest.transitionState) + assertThat(transitionState) + .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT)) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/OWNERS new file mode 100644 index 000000000000..cd04e82d7816 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/OWNERS @@ -0,0 +1,3 @@ +set noparent + +include /packages/SystemUI/src/com/android/systemui/qs/OWNERS diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt new file mode 100644 index 000000000000..30d1822b28da --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.data.restoreprocessors + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class WorkTileRestoreProcessorTest : SysuiTestCase() { + + private val underTest = WorkTileRestoreProcessor() + @Test + fun restoreWithWorkTile_removeTracking() = runTest { + val removeTracking by collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER))) + runCurrent() + + val restoreData = + RestoreData( + restoredTiles = listOf(TILE_SPEC), + restoredAutoAddedTiles = setOf(TILE_SPEC), + USER, + ) + + underTest.postProcessRestore(restoreData) + + assertThat(removeTracking).isEqualTo(Unit) + } + + @Test + fun restoreWithWorkTile_otherUser_noRemoveTracking() = runTest { + val removeTracking by + collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER + 1))) + runCurrent() + + val restoreData = + RestoreData( + restoredTiles = listOf(TILE_SPEC), + restoredAutoAddedTiles = setOf(TILE_SPEC), + USER, + ) + + underTest.postProcessRestore(restoreData) + + assertThat(removeTracking).isNull() + } + + @Test + fun restoreWithoutWorkTile_noSignal() = runTest { + val removeTracking by collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER))) + runCurrent() + + val restoreData = + RestoreData( + restoredTiles = emptyList(), + restoredAutoAddedTiles = emptySet(), + USER, + ) + + underTest.postProcessRestore(restoreData) + + assertThat(removeTracking).isNull() + } + + companion object { + private const val USER = 10 + private val TILE_SPEC = TileSpec.Companion.create("work") + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt index adccc84e494b..c7e7845f206c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt @@ -25,6 +25,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor +import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking import com.android.systemui.qs.pipeline.shared.TileSpec @@ -32,25 +37,28 @@ import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.settings.FakeUserTracker import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class WorkTileAutoAddableTest : SysuiTestCase() { + private val kosmos = Kosmos() + + private val restoreProcessor: RestoreProcessor + get() = kosmos.workTileRestoreProcessor + private lateinit var userTracker: FakeUserTracker private lateinit var underTest: WorkTileAutoAddable @Before fun setup() { - MockitoAnnotations.initMocks(this) - userTracker = FakeUserTracker( _userId = USER_INFO_0.id, @@ -58,7 +66,7 @@ class WorkTileAutoAddableTest : SysuiTestCase() { _userProfiles = listOf(USER_INFO_0) ) - underTest = WorkTileAutoAddable(userTracker) + underTest = WorkTileAutoAddable(userTracker, kosmos.workTileRestoreProcessor) } @Test @@ -114,10 +122,80 @@ class WorkTileAutoAddableTest : SysuiTestCase() { assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always) } + @Test + fun restoreDataWithWorkTile_noCurrentManagedProfile_triggersRemove() = runTest { + val userId = 0 + val signal by collectLastValue(underTest.autoAddSignal(userId)) + runCurrent() + + val restoreData = createRestoreWithWorkTile(userId) + + restoreProcessor.postProcessRestore(restoreData) + + assertThat(signal!!).isEqualTo(AutoAddSignal.RemoveTracking(SPEC)) + } + + @Test + fun restoreDataWithWorkTile_currentlyManagedProfile_doesntTriggerRemove() = runTest { + userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0) + val userId = 0 + val signals by collectValues(underTest.autoAddSignal(userId)) + runCurrent() + + val restoreData = createRestoreWithWorkTile(userId) + + restoreProcessor.postProcessRestore(restoreData) + + assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC)) + } + + @Test + fun restoreDataWithoutWorkTile_noManagedProfile_doesntTriggerRemove() = runTest { + val userId = 0 + val signals by collectValues(underTest.autoAddSignal(userId)) + runCurrent() + + val restoreData = createRestoreWithoutWorkTile(userId) + + restoreProcessor.postProcessRestore(restoreData) + + assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC)) + } + + @Test + fun restoreDataWithoutWorkTile_managedProfile_doesntTriggerRemove() = runTest { + userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0) + val userId = 0 + val signals by collectValues(underTest.autoAddSignal(userId)) + runCurrent() + + val restoreData = createRestoreWithoutWorkTile(userId) + + restoreProcessor.postProcessRestore(restoreData) + + assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC)) + } + companion object { private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC) private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL) private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL) private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE) + + private fun createRestoreWithWorkTile(userId: Int): RestoreData { + return RestoreData( + listOf(TileSpec.create("a"), SPEC, TileSpec.create("b")), + setOf(SPEC), + userId, + ) + } + + private fun createRestoreWithoutWorkTile(userId: Int): RestoreData { + return RestoreData( + listOf(TileSpec.create("a"), TileSpec.create("b")), + emptySet(), + userId, + ) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt index 41a7ec03408d..54b03a90229b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt @@ -183,6 +183,22 @@ class AutoAddInteractorTest : SysuiTestCase() { assertThat(autoAddedTiles).contains(SPEC) } + @Test + fun autoAddable_removeTrackingSignal_notRemovedButUnmarked() = + testScope.runTest { + autoAddRepository.markTileAdded(USER, SPEC) + val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER)) + val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always) + + underTest = createInteractor(setOf(fakeAutoAddable)) + + fakeAutoAddable.sendRemoveTrackingSignal(USER) + runCurrent() + + verify(currentTilesInteractor, never()).removeTiles(any()) + assertThat(autoAddedTiles).doesNotContain(SPEC) + } + private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor { return AutoAddInteractor( autoAddables, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt index f73cab8a10a3..b2a9783d2e60 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt @@ -5,10 +5,15 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository import com.android.systemui.qs.pipeline.data.repository.FakeQSSettingsRestoredRepository import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter +import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractorTest.TestableRestoreProcessor.Companion.POSTPROCESS +import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractorTest.TestableRestoreProcessor.Companion.PREPROCESS +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -17,7 +22,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.MockitoAnnotations +import org.mockito.Mockito.inOrder @RunWith(AndroidJUnit4::class) @SmallTest @@ -28,6 +33,9 @@ class RestoreReconciliationInteractorTest : SysuiTestCase() { private val qsSettingsRestoredRepository = FakeQSSettingsRestoredRepository() + private val restoreProcessor: TestableRestoreProcessor = TestableRestoreProcessor() + private val qsLogger: QSPipelineLogger = mock() + private lateinit var underTest: RestoreReconciliationInteractor private val testDispatcher = StandardTestDispatcher() @@ -35,13 +43,13 @@ class RestoreReconciliationInteractorTest : SysuiTestCase() { @Before fun setUp() { - MockitoAnnotations.initMocks(this) - underTest = RestoreReconciliationInteractor( tileSpecRepository, autoAddRepository, qsSettingsRestoredRepository, + setOf(restoreProcessor), + qsLogger, testScope.backgroundScope, testDispatcher ) @@ -85,6 +93,44 @@ class RestoreReconciliationInteractorTest : SysuiTestCase() { assertThat(autoAdd).isEqualTo(expectedAutoAdd.toTilesSet()) } + @Test + fun restoreProcessorsCalled() = + testScope.runTest { + val user = 10 + + val restoredSpecs = "a,c,d,f" + val restoredAutoAdded = "d,e" + + val restoreData = + RestoreData( + restoredSpecs.toTilesList(), + restoredAutoAdded.toTilesSet(), + user, + ) + + qsSettingsRestoredRepository.onDataRestored(restoreData) + runCurrent() + + assertThat(restoreProcessor.calls).containsExactly(PREPROCESS, POSTPROCESS).inOrder() + } + + private class TestableRestoreProcessor : RestoreProcessor { + val calls = mutableListOf<Any>() + + override suspend fun preProcessRestore(restoreData: RestoreData) { + calls.add(PREPROCESS) + } + + override suspend fun postProcessRestore(restoreData: RestoreData) { + calls.add(POSTPROCESS) + } + + companion object { + val PREPROCESS = Any() + val POSTPROCESS = Any() + } + } + companion object { private fun String.toTilesList() = TilesSettingConverter.toTilesList(this) private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt new file mode 100644 index 000000000000..96d57743e2ee --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.interactor + +import android.content.pm.UserInfo +import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository +import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.qsTileFactory +import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.settings.userTracker +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This integration test is for testing the solution to b/314781280. In particular, there are two + * issues we want to verify after a restore of a device with a work profile and a work mode tile: + * * When the work profile is re-enabled in the target device, it is auto-added. + * * The tile is auto-added in the same position that it was in the restored device. + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() { + + private val kosmos = Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } + // Getter here so it can change when there is a managed profile. + private val workTileAvailable: Boolean + get() = hasManagedProfile() + private val currentUser: Int + get() = kosmos.userTracker.userId + + private val testScope: TestScope + get() = kosmos.testScope + + @Before + fun setUp() { + mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE) + + kosmos.qsTileFactory = FakeQSFactory(::tileCreator) + kosmos.restoreReconciliationInteractor.start() + kosmos.autoAddInteractor.init(kosmos.currentTilesInteractor) + } + + @Test + fun workTileRestoredAndPreviouslyAutoAdded_notAvailable_willBeAutoaddedInCorrectPosition() = + testScope.runTest { + val tiles by collectLastValue(kosmos.currentTilesInteractor.currentTiles) + + // Set up + val currentTiles = listOf("a".toTileSpec()) + kosmos.fakeTileSpecRepository.setTiles(currentUser, currentTiles) + + val restoredTiles = + listOf(WORK_TILE_SPEC) + listOf("b", "c", "d").map { it.toTileSpec() } + val restoredAutoAdded = setOf(WORK_TILE_SPEC) + + val restoreData = RestoreData(restoredTiles, restoredAutoAdded, currentUser) + + // WHEN we restore tiles that auto-added the WORK tile and it's not available (there + // are no managed profiles) + kosmos.fakeRestoreRepository.onDataRestored(restoreData) + + // THEN the work tile is not part of the current tiles + assertThat(tiles!!).hasSize(3) + assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC) + + // WHEN we add a work profile + createManagedProfileAndAdd() + + // THEN the work profile is added in the correct place + assertThat(tiles!!.first().spec).isEqualTo(WORK_TILE_SPEC) + } + + @Test + fun workTileNotRestoredAndPreviouslyAutoAdded_wontBeAutoAddedWhenWorkProfileIsAdded() = + testScope.runTest { + val tiles by collectLastValue(kosmos.currentTilesInteractor.currentTiles) + + // Set up + val currentTiles = listOf("a".toTileSpec()) + kosmos.fakeTileSpecRepository.setTiles(currentUser, currentTiles) + runCurrent() + + val restoredTiles = listOf("b", "c", "d").map { it.toTileSpec() } + val restoredAutoAdded = setOf(WORK_TILE_SPEC) + + val restoreData = RestoreData(restoredTiles, restoredAutoAdded, currentUser) + + // WHEN we restore tiles that auto-added the WORK tile + kosmos.fakeRestoreRepository.onDataRestored(restoreData) + + // THEN the work tile is not part of the current tiles + assertThat(tiles!!).hasSize(3) + assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC) + + // WHEN we add a work profile + createManagedProfileAndAdd() + + // THEN the work profile is not added because the user had manually removed it in the + // past + assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC) + } + + private fun tileCreator(spec: String): QSTile { + return if (spec == WORK_TILE_SPEC.spec) { + FakeQSTile(currentUser, workTileAvailable) + } else { + FakeQSTile(currentUser) + } + } + + private fun hasManagedProfile(): Boolean { + return kosmos.userTracker.userProfiles.any { it.isManagedProfile } + } + + private fun TestScope.createManagedProfileAndAdd() { + kosmos.fakeUserTracker.set( + listOf(USER_0_INFO, MANAGED_USER_INFO), + 0, + ) + runCurrent() + } + + private companion object { + val WORK_TILE_SPEC = "work".toTileSpec() + val USER_0_INFO = + UserInfo( + 0, + "zero", + "", + UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL, + ) + val MANAGED_USER_INFO = + UserInfo( + 10, + "ten-managed", + "", + 0, + UserManager.USER_TYPE_PROFILE_MANAGED, + ) + + fun String.toTileSpec() = TileSpec.create(this) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt index 2b744ac8398a..00405d0a07e7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.alarm.domain import android.app.AlarmManager +import android.graphics.drawable.TestStubDrawable import android.widget.Switch import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -40,7 +41,14 @@ class AlarmTileMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val alarmTileConfig = kosmos.qsAlarmTileConfig // Using lazy (versus =) to make sure we override the right context -- see b/311612168 - private val mapper by lazy { AlarmTileMapper(context.orCreateTestableResources.resources) } + private val mapper by lazy { + AlarmTileMapper( + context.orCreateTestableResources + .apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) } + .resources, + context.theme + ) + } @Test fun notAlarmSet() { @@ -100,7 +108,7 @@ class AlarmTileMapperTest : SysuiTestCase() { ): QSTileState { val label = context.getString(R.string.status_bar_alarm) return QSTileState( - { Icon.Resource(R.drawable.ic_alarm, null) }, + { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) }, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt index 7b2ac90b9766..b60f483cccbb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.flashlight.domain +import android.graphics.drawable.TestStubDrawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -35,7 +36,17 @@ import org.junit.runner.RunWith class FlashlightMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val qsTileConfig = kosmos.qsFlashlightTileConfig - private val mapper by lazy { FlashlightMapper(context.orCreateTestableResources.resources) } + private val mapper by lazy { + FlashlightMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_flashlight_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable()) + } + .resources, + context.theme + ) + } @Test fun mapsDisabledDataToInactiveState() { @@ -56,20 +67,20 @@ class FlashlightMapperTest : SysuiTestCase() { @Test fun mapsEnabledDataToOnIconState() { - val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_on, null) - val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true)) + val expectedIcon = + Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null) val actualIcon = tileState.icon() assertThat(actualIcon).isEqualTo(expectedIcon) } @Test fun mapsDisabledDataToOffIconState() { - val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_off, null) - val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false)) + val expectedIcon = + Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null) val actualIcon = tileState.icon() assertThat(actualIcon).isEqualTo(expectedIcon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt index 8791877f8863..ea74a4c0d398 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.location.domain +import android.graphics.drawable.TestStubDrawable import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -36,7 +37,17 @@ class LocationTileMapperTest : SysuiTestCase() { private val kosmos = Kosmos() private val qsTileConfig = kosmos.qsLocationTileConfig - private val mapper by lazy { LocationTileMapper(context.orCreateTestableResources.resources) } + private val mapper by lazy { + LocationTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_location_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_location_icon_on, TestStubDrawable()) + } + .resources, + context.theme + ) + } @Test fun mapsDisabledDataToInactiveState() { @@ -56,20 +67,18 @@ class LocationTileMapperTest : SysuiTestCase() { @Test fun mapsEnabledDataToOnIconState() { - val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_on, null) - val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true)) + val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null) val actualIcon = tileState.icon() Truth.assertThat(actualIcon).isEqualTo(expectedIcon) } @Test fun mapsDisabledDataToOffIconState() { - val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_off, null) - val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false)) + val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null) val actualIcon = tileState.icon() Truth.assertThat(actualIcon).isEqualTo(expectedIcon) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt index d1824129590b..d162c778f607 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.saver.domain +import android.graphics.drawable.TestStubDrawable import android.widget.Switch import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -37,7 +38,17 @@ class DataSaverTileMapperTest : SysuiTestCase() { private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig // Using lazy (versus =) to make sure we override the right context -- see b/311612168 - private val mapper by lazy { DataSaverTileMapper(context.orCreateTestableResources.resources) } + private val mapper by lazy { + DataSaverTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_data_saver_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable()) + } + .resources, + context.theme + ) + } @Test fun activeStateMatchesEnabledModel() { @@ -80,7 +91,7 @@ class DataSaverTileMapperTest : SysuiTestCase() { else context.resources.getStringArray(R.array.tile_states_saver)[0] return QSTileState( - { Icon.Resource(iconRes, null) }, + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt index 87f50090e58b..a9776068b20c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.uimodenight.domain import android.app.UiModeManager +import android.graphics.drawable.TestStubDrawable import android.text.TextUtils import android.view.View import android.widget.Switch @@ -41,7 +42,15 @@ class UiModeNightTileMapperTest : SysuiTestCase() { private val qsTileConfig = kosmos.qsUiModeNightTileConfig private val mapper by lazy { - UiModeNightTileMapper(context.orCreateTestableResources.resources) + UiModeNightTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_light_dark_theme_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable()) + } + .resources, + context.theme + ) } private fun createUiNightModeTileState( @@ -60,7 +69,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() { expandedAccessibilityClass: KClass<out View>? = Switch::class, ): QSTileState { return QSTileState( - { Icon.Resource(iconRes, null) }, + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, label, activationState, secondaryLabel, diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml deleted file mode 100644 index 02e10cd4ad7c..000000000000 --- a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_focused="true"> - <shape android:shape="rectangle"> - <corners android:radius="16dp" /> - <stroke android:width="3dp" - android:color="@color/bouncer_password_focus_color" /> - </shape> - </item> -</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml index 0b35559148af..66c54f2a668e 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml @@ -23,7 +23,7 @@ android:layout_marginTop="@dimen/keyguard_lock_padding" android:importantForAccessibility="no" android:ellipsize="marquee" - android:focusable="false" + android:focusable="true" android:gravity="center" android:singleLine="true" /> </merge> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 6e6709f94abb..88f7bcd5d907 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -76,7 +76,6 @@ </style> <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item> <item name="android:gravity">center</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index a22fd18fc2c0..bcc3c83b4560 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -93,9 +93,6 @@ <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color> <!-- Color of background circle of user avatars in quick settings user switcher --> <color name="qs_user_switcher_avatar_background">#3C4043</color> - <!-- Color of border for keyguard password input when focused --> - <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color> - <!-- Accessibility floating menu --> <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% --> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 462fc95b8cd1..5f6a39a91b8b 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -56,8 +56,6 @@ <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color> <!-- Color of background circle of user avatars in keyguard user switcher --> <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color> - <!-- Color of border for keyguard password input when focused --> - <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color> <!-- Icon color for user avatars in user switcher quick settings --> <color name="qs_user_switcher_avatar_icon_color">#3C4043</color> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 9764de1993e5..36fe75f69a45 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -168,6 +168,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); + mPasswordEntry.setDefaultFocusHighlightEnabled(false); mOkButton = findViewById(R.id.key_enter); diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index 3119b9e98bac..1f4be4060223 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -18,18 +18,26 @@ package com.android.systemui.communal.data.repository import com.android.systemui.Flags.communalHub import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** Encapsulates the state of communal mode. */ interface CommunalRepository { @@ -45,14 +53,26 @@ interface CommunalRepository { */ val desiredScene: StateFlow<CommunalSceneKey> + /** Exposes the transition state of the communal [SceneTransitionLayout]. */ + val transitionState: StateFlow<ObservableCommunalTransitionState> + /** Updates the requested scene. */ fun setDesiredScene(desiredScene: CommunalSceneKey) + + /** + * Updates the transition state of the hub [SceneTransitionLayout]. + * + * Note that you must call is with `null` when the UI is done or risk a memory leak. + */ + fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) } +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class CommunalRepositoryImpl @Inject constructor( + @Background backgroundScope: CoroutineScope, private val featureFlagsClassic: FeatureFlagsClassic, sceneContainerFlags: SceneContainerFlags, sceneContainerRepository: SceneContainerRepository, @@ -61,13 +81,34 @@ constructor( get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub() private val _desiredScene: MutableStateFlow<CommunalSceneKey> = - MutableStateFlow(CommunalSceneKey.Blank) + MutableStateFlow(CommunalSceneKey.DEFAULT) override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow() + private val defaultTransitionState = + ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT) + private val _transitionState = MutableStateFlow<Flow<ObservableCommunalTransitionState>?>(null) + override val transitionState: StateFlow<ObservableCommunalTransitionState> = + _transitionState + .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } + .stateIn( + scope = backgroundScope, + started = SharingStarted.Lazily, + initialValue = defaultTransitionState, + ) + override fun setDesiredScene(desiredScene: CommunalSceneKey) { _desiredScene.value = desiredScene } + /** + * Updates the transition state of the hub [SceneTransitionLayout]. + * + * Note that you must call is with `null` when the UI is done or risk a memory leak. + */ + override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) { + _transitionState.value = transitionState + } + override val isCommunalHubShowing: Flow<Boolean> = if (sceneContainerFlags.isEnabled()) { sceneContainerRepository.desiredScene.map { scene -> scene.key == SceneKey.Communal } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index e630fd4e3c54..1a2a4253e761 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -62,6 +63,19 @@ constructor( */ val desiredScene: StateFlow<CommunalSceneKey> = communalRepository.desiredScene + /** Transition state of the hub mode. */ + val transitionState: StateFlow<ObservableCommunalTransitionState> = + communalRepository.transitionState + + /** + * Updates the transition state of the hub [SceneTransitionLayout]. + * + * Note that you must call is with `null` when the UI is done or risk a memory leak. + */ + fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) { + communalRepository.setTransitionState(transitionState) + } + /** * Flow that emits a boolean if the communal UI is showing, ie. the [desiredScene] is the * [CommunalSceneKey.Communal]. diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt index 2be909c8e6d0..c68dd4ff271c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalSceneKey.kt @@ -29,4 +29,8 @@ sealed class CommunalSceneKey( override fun toString(): String { return loggingName } + + companion object { + val DEFAULT = Blank + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt new file mode 100644 index 000000000000..d834715768c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/ObservableCommunalTransitionState.kt @@ -0,0 +1,54 @@ +/* + * 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.communal.shared.model + +import kotlinx.coroutines.flow.Flow + +/** + * This is a fork of the `com.android.compose.animation.scene.ObservableTransitionState` class. + * + * TODO(b/315490861): remove this fork, once we can compile Compose into System UI. + */ +sealed class ObservableCommunalTransitionState { + /** No transition/animation is currently running. */ + data class Idle(val scene: CommunalSceneKey) : ObservableCommunalTransitionState() + + /** There is a transition animating between two scenes. */ + data class Transition( + val fromScene: CommunalSceneKey, + val toScene: CommunalSceneKey, + val progress: Flow<Float>, + + /** + * Whether the transition was originally triggered by user input rather than being + * programmatic. If this value is initially true, it will remain true until the transition + * fully completes, even if the user input that triggered the transition has ended. Any + * sub-transitions launched by this one will inherit this value. For example, if the user + * drags a pointer but does not exceed the threshold required to transition to another + * scene, this value will remain true after the pointer is no longer touching the screen and + * will be true in any transition created to animate back to the original position. + */ + val isInitiatedByUserInput: Boolean, + + /** + * Whether user input is currently driving the transition. For example, if a user is + * dragging a pointer, this emits true. Once they lift their finger, this emits false while + * the transition completes/settles. + */ + val isUserInputOngoing: Flow<Boolean>, + ) : ObservableCommunalTransitionState() +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 333fc194b288..708f137017ca 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -22,6 +22,7 @@ import android.view.MotionEvent import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.shade.ShadeViewController import javax.inject.Provider @@ -43,6 +44,15 @@ abstract class BaseCommunalViewModel( communalInteractor.onSceneChanged(scene) } + /** + * Updates the transition state of the hub [SceneTransitionLayout]. + * + * Note that you must call is with `null` when the UI is done or risk a memory leak. + */ + fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) { + communalInteractor.setTransitionState(transitionState) + } + // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block // touches anymore. /** Called when a touch is received outside the edge swipe area when hub mode is closed. */ diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index d5b95d6721f9..5ec51f4c3dad 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -16,6 +16,12 @@ package com.android.systemui.flags +import com.android.server.notification.Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS +import com.android.server.notification.Flags.FLAG_POLITE_NOTIFICATIONS +import com.android.server.notification.Flags.FLAG_VIBRATE_WHILE_UNLOCKED +import com.android.server.notification.Flags.crossAppPoliteNotifications +import com.android.server.notification.Flags.politeNotifications +import com.android.server.notification.Flags.vibrateWhileUnlocked import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.dagger.SysUISingleton @@ -36,5 +42,14 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha val keyguardBottomAreaRefactor = FlagToken( FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()) KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor + + val crossAppPoliteNotifToken = + FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications()) + val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications()) + crossAppPoliteNotifToken dependsOn politeNotifToken + + val vibrateWhileUnlockedToken = + FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked()) + vibrateWhileUnlockedToken dependsOn politeNotifToken } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index b753ff742100..b1d4587c20d8 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -111,7 +111,7 @@ object Flags { // TODO(b/301955929) @JvmField val NOTIF_LS_BACKGROUND_THREAD = - unreleasedFlag("notification_lockscreen_mgr_bg_thread", teamfood = true) + releasedFlag("notification_lockscreen_mgr_bg_thread") // 200 - keyguard/lockscreen // ** Flag retired ** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index b51edab6dfe8..0df7f9b809fd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -160,6 +160,9 @@ interface KeyguardRepository { /** Last point that [KeyguardRootView] was tapped */ val lastRootViewTapPosition: MutableStateFlow<Point?> + /** Is the ambient indication area visible? */ + val ambientIndicationVisible: MutableStateFlow<Boolean> + /** Observable for the [StatusBarState] */ val statusBarState: StateFlow<StatusBarState> @@ -423,6 +426,8 @@ constructor( override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null) + override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val isDreamingWithOverlay: Flow<Boolean> = conflatedCallbackFlow { val callback = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index c12efe875b0b..defca18b64b8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -174,6 +174,9 @@ constructor( /** Last point that [KeyguardRootView] view was tapped */ val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow() + /** Is the ambient indication area visible? */ + val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow() + /** Whether the primary bouncer is showing or not. */ val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow @@ -311,6 +314,10 @@ constructor( repository.lastRootViewTapPosition.value = point } + fun setAmbientIndicationVisible(isVisible: Boolean) { + repository.ambientIndicationVisible.value = isVisible + } + companion object { private const val TAG = "KeyguardInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 1d4520ff8f03..26dace00ad76 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -180,7 +180,9 @@ constructor( goneToAodTransitionViewModel .enterFromTopTranslationY(enterFromTopAmount) .onStart { emit(0f) }, - occludedToLockscreenTransitionViewModel.lockscreenTranslationY, + occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart { + emit(0f) + }, ) { keyguardTransitionY, burnInTranslationY, @@ -193,6 +195,7 @@ constructor( occludedToLockscreenTransitionTranslationY } } + .distinctUntilChanged() val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt index fdc70a83e8b1..76ef8a2b813c 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt @@ -278,7 +278,12 @@ class PrivacyDialogControllerV2( d.setShowForAllUsers(true) d.addOnDismissListener(onDialogDismissed) if (view != null) { - dialogLaunchAnimator.showFromView(d, view) + val controller = getPrivacyDialogController(view) + if (controller == null) { + d.show() + } else { + dialogLaunchAnimator.show(d, controller) + } } else { d.show() } @@ -291,6 +296,13 @@ class PrivacyDialogControllerV2( } } + private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? { + val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null + return object : DialogLaunchAnimator.Controller by delegate { + override fun shouldAnimateExit() = false + } + } + /** Dismisses the dialog */ fun dismissDialog() { dialog?.dismiss() diff --git a/packages/SystemUI/src/com/android/systemui/qs/OWNERS b/packages/SystemUI/src/com/android/systemui/qs/OWNERS new file mode 100644 index 000000000000..45a4b5035c45 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/OWNERS @@ -0,0 +1,10 @@ +set noparent + +# Bug component: 78010 + +apotapov@google.com +asc@google.com +bhnm@google.com +kozynski@google.com +ostonge@google.com +pixel@google.com
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index b50798e59953..4bad45f19673 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt @@ -20,6 +20,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository @@ -39,14 +40,17 @@ import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import dagger.multibindings.Multibinds -@Module(includes = [QSAutoAddModule::class]) +@Module(includes = [QSAutoAddModule::class, RestoreProcessorsModule::class]) abstract class QSPipelineModule { /** Implementation for [TileSpecRepository] */ @Binds abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository + @Multibinds abstract fun provideRestoreProcessors(): Set<RestoreProcessor> + @Binds abstract fun provideDefaultTilesRepository( impl: DefaultTilesQSHostRepository diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt new file mode 100644 index 000000000000..e970c84d166f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.dagger + +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor +import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +interface RestoreProcessorsModule { + + @Binds + @IntoSet + fun bindWorkTileRestoreProcessor(impl: WorkTileRestoreProcessor): RestoreProcessor +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt new file mode 100644 index 000000000000..8f7de1976b36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.data.model + +/** + * Perform processing of the [RestoreData] before or after it's applied to repositories. + * + * The order in which the restore processors are applied in not deterministic. + * + * In order to declare a restore processor, add it in [RestoreProcessingModule] using + * + * ``` + * @Binds + * @IntoSet + * `` + */ +interface RestoreProcessor { + + /** Should be called before applying the restore to the necessary repositories */ + suspend fun preProcessRestore(restoreData: RestoreData) {} + + /** Should be called after requesting the repositories to update. */ + suspend fun postProcessRestore(restoreData: RestoreData) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt index 7998dfbe3f92..d40f3f4db5f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt @@ -20,9 +20,8 @@ import android.util.SparseArray import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.shared.TileSpec -import kotlinx.coroutines.flow.Flow import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow /** Repository to track what QS tiles have been auto-added */ interface AutoAddRepository { @@ -49,8 +48,9 @@ interface AutoAddRepository { @SysUISingleton class AutoAddSettingRepository @Inject -constructor(private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory) : - AutoAddRepository { +constructor( + private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory, +) : AutoAddRepository { private val userAutoAddRepositories = SparseArray<UserAutoAddRepository>() diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt index 6cee1161a104..e718eea09665 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt @@ -10,6 +10,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository.Companion.BUFFER_CAPACITY import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import javax.inject.Inject @@ -28,6 +29,14 @@ import kotlinx.coroutines.flow.shareIn /** Provides restored data (from Backup and Restore) for Quick Settings pipeline */ interface QSSettingsRestoredRepository { val restoreData: Flow<RestoreData> + + companion object { + // This capacity is the number of restore data that we will keep buffered in the shared + // flow. It is unlikely that at any given time there would be this many restores being + // processed by consumers, but just in case that a couple of users are restored at the + // same time and they need to be replayed for the consumers of the flow. + const val BUFFER_CAPACITY = 10 + } } @SysUISingleton @@ -86,11 +95,6 @@ constructor( private companion object { private const val TAG = "QSSettingsRestoredBroadcastRepository" - // This capacity is the number of restore data that we will keep buffered in the shared - // flow. It is unlikely that at any given time there would be this many restores being - // processed by consumers, but just in case that a couple of users are restored at the - // same time and they need to be replayed for the consumers of the flow. - private const val BUFFER_CAPACITY = 10 private val INTENT_FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED) private const val TILES_SETTING = Settings.Secure.QS_TILES diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt new file mode 100644 index 000000000000..7376aa90883f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.data.restoreprocessors + +import android.os.UserHandle +import android.util.SparseIntArray +import androidx.annotation.GuardedBy +import androidx.core.util.getOrDefault +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor +import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.WorkModeTile +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map + +/** + * Processor for restore data for work tile. + * + * It will indicate when auto-add tracking may be removed for a user. This may be necessary if the + * tile will be destroyed due to being not available, but needs to be added once work profile is + * enabled (after restore), in the same position as it was in the restored data. + */ +@SysUISingleton +class WorkTileRestoreProcessor @Inject constructor() : RestoreProcessor { + + @GuardedBy("lastRestorePosition") private val lastRestorePosition = SparseIntArray() + + private val _removeTrackingForUser = + MutableSharedFlow<Int>(extraBufferCapacity = QSSettingsRestoredRepository.BUFFER_CAPACITY) + + /** + * Flow indicating that we may need to remove auto-add tracking for the work tile for a given + * user. + */ + fun removeTrackingForUser(userHandle: UserHandle): Flow<Unit> { + return _removeTrackingForUser.filter { it == userHandle.identifier }.map {} + } + + override suspend fun postProcessRestore(restoreData: RestoreData) { + if (TILE_SPEC in restoreData.restoredTiles) { + synchronized(lastRestorePosition) { + lastRestorePosition.put( + restoreData.userId, + restoreData.restoredTiles.indexOf(TILE_SPEC) + ) + } + _removeTrackingForUser.emit(restoreData.userId) + } + } + + fun pollLastPosition(userId: Int): Int { + return synchronized(lastRestorePosition) { + lastRestorePosition.getOrDefault(userId, TileSpecRepository.POSITION_AT_END).also { + lastRestorePosition.delete(userId) + } + } + } + + companion object { + private val TILE_SPEC = TileSpec.create(WorkModeTile.TILE_SPEC) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt index 5e3c34841c50..b22119966460 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt @@ -17,8 +17,10 @@ package com.android.systemui.qs.pipeline.domain.autoaddable import android.content.pm.UserInfo +import android.os.UserHandle import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking import com.android.systemui.qs.pipeline.domain.model.AutoAddable @@ -28,6 +30,8 @@ import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.merge /** * [AutoAddable] for [WorkModeTile.TILE_SPEC]. @@ -36,17 +40,37 @@ import kotlinx.coroutines.flow.Flow * signal to remove it if there is not. */ @SysUISingleton -class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable { +class WorkTileAutoAddable +@Inject +constructor( + private val userTracker: UserTracker, + private val workTileRestoreProcessor: WorkTileRestoreProcessor, +) : AutoAddable { private val spec = TileSpec.create(WorkModeTile.TILE_SPEC) override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> { - return conflatedCallbackFlow { + val removeTrackingDueToRestore: Flow<AutoAddSignal> = + workTileRestoreProcessor.removeTrackingForUser(UserHandle.of(userId)).mapNotNull { + val profiles = userTracker.userProfiles + if (profiles.any { it.id == userId } && !profiles.any { it.isManagedProfile }) { + // Only remove auto-added if there are no managed profiles for this user + AutoAddSignal.RemoveTracking(spec) + } else { + null + } + } + val signalsFromCallback = conflatedCallbackFlow { fun maybeSend(profiles: List<UserInfo>) { if (profiles.any { it.id == userId }) { // We are looking at the profiles of the correct user. if (profiles.any { it.isManagedProfile }) { - trySend(AutoAddSignal.Add(spec)) + trySend( + AutoAddSignal.Add( + spec, + workTileRestoreProcessor.pollLastPosition(userId), + ) + ) } else { trySend(AutoAddSignal.Remove(spec)) } @@ -65,6 +89,7 @@ class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTrack awaitClose { userTracker.removeCallback(callback) } } + return merge(removeTrackingDueToRestore, signalsFromCallback) } override val autoAddTracking = AutoAddTracking.Always diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt index b74739322fcd..cde38359a871 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt @@ -103,6 +103,10 @@ constructor( qsPipelineLogger.logTileAutoRemoved(userId, signal.spec) repository.unmarkTileAdded(userId, signal.spec) } + is AutoAddSignal.RemoveTracking -> { + qsPipelineLogger.logTileUnmarked(userId, signal.spec) + repository.unmarkTileAdded(userId, signal.spec) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt index 9844903eff26..a5be14ec3776 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt @@ -3,14 +3,15 @@ package com.android.systemui.qs.pipeline.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.data.model.RestoreProcessor import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.take @@ -33,6 +34,8 @@ constructor( private val tileSpecRepository: TileSpecRepository, private val autoAddRepository: AutoAddRepository, private val qsSettingsRestoredRepository: QSSettingsRestoredRepository, + private val restoreProcessors: Set<@JvmSuppressWildcards RestoreProcessor>, + private val qsPipelineLogger: QSPipelineLogger, @Application private val applicationScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { @@ -40,14 +43,30 @@ constructor( @OptIn(ExperimentalCoroutinesApi::class) fun start() { applicationScope.launch(backgroundDispatcher) { - qsSettingsRestoredRepository.restoreData.flatMapConcat { data -> - autoAddRepository.autoAddedTiles(data.userId) - .take(1) - .map { tiles -> data to tiles } - }.collect { (restoreData, autoAdded) -> - tileSpecRepository.reconcileRestore(restoreData, autoAdded) - autoAddRepository.reconcileRestore(restoreData) - } + qsSettingsRestoredRepository.restoreData + .flatMapConcat { data -> + autoAddRepository.autoAddedTiles(data.userId).take(1).map { tiles -> + data to tiles + } + } + .collect { (restoreData, autoAdded) -> + restoreProcessors.forEach { + it.preProcessRestore(restoreData) + qsPipelineLogger.logRestoreProcessorApplied( + it::class.simpleName, + QSPipelineLogger.RestorePreprocessorStep.PREPROCESSING, + ) + } + tileSpecRepository.reconcileRestore(restoreData, autoAdded) + autoAddRepository.reconcileRestore(restoreData) + restoreProcessors.forEach { + it.postProcessRestore(restoreData) + qsPipelineLogger.logRestoreProcessorApplied( + it::class.simpleName, + QSPipelineLogger.RestorePreprocessorStep.POSTPROCESSING, + ) + } + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt index ed7b8bd4c2f4..8263680d7fad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt @@ -34,4 +34,9 @@ sealed interface AutoAddSignal { data class Remove( override val spec: TileSpec, ) : AutoAddSignal + + /** Signal for remove the auto-add marker from the tile, but not remove the tile */ + data class RemoveTracking( + override val spec: TileSpec, + ) : AutoAddSignal } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt index bca86c9ee8af..7d2c6c8f70fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt @@ -209,6 +209,18 @@ constructor( ) } + fun logTileUnmarked(userId: Int, spec: TileSpec) { + tileAutoAddLogBuffer.log( + AUTO_ADD_TAG, + LogLevel.DEBUG, + { + int1 = userId + str1 = spec.toString() + }, + { "Tile $str1 unmarked as auto-added for user $int1" } + ) + } + fun logSettingsRestored(restoreData: RestoreData) { restoreLogBuffer.log( RESTORE_TAG, @@ -226,6 +238,21 @@ constructor( ) } + fun logRestoreProcessorApplied( + restoreProcessorClassName: String?, + step: RestorePreprocessorStep, + ) { + restoreLogBuffer.log( + RESTORE_TAG, + LogLevel.DEBUG, + { + str1 = restoreProcessorClassName + str2 = step.name + }, + { "Restore $str2 processed by $str1" } + ) + } + /** Reasons for destroying an existing tile. */ enum class TileDestroyedReason(val readable: String) { TILE_REMOVED("Tile removed from current set"), @@ -234,4 +261,9 @@ constructor( EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"), TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"), } + + enum class RestorePreprocessorStep { + PREPROCESSING, + POSTPROCESSING + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt index 09d7a1f7142d..17b78ebf106c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt @@ -24,7 +24,7 @@ interface QSTileUserActionInteractor<DATA_TYPE> { * [QSTileInput.data]. It's guaranteed that [QSTileInput.userId] is the same as the id passed to * [QSTileDataInteractor] to get [QSTileInput.data]. * - * It's safe to run long running computations inside this function in this. + * It's safe to run long running computations inside this function. */ @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt index 4a34276671c1..b325b4daeb81 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.di +import android.content.Context +import android.content.res.Resources.Theme import com.android.systemui.qs.external.CustomTileStatePersister import com.android.systemui.qs.external.CustomTileStatePersisterImpl import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler @@ -27,6 +29,7 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProviderImpl import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import dagger.Binds import dagger.Module +import dagger.Provides import dagger.multibindings.Multibinds /** Module listing subcomponents */ @@ -57,4 +60,9 @@ interface QSTilesModule { @Binds fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister + + companion object { + + @Provides fun provideTilesTheme(context: Context): Theme = context.theme + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 2350b5dce8b8..9d214e7141a8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -59,12 +59,12 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.Prefs; -import com.android.systemui.res.R; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wifitrackerlib.WifiEntry; @@ -157,14 +157,6 @@ public class InternetDialog extends SystemUIDialog implements // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; - protected boolean mIsSearchingHidden; - protected final Runnable mHideProgressBarRunnable = () -> { - setProgressBarVisible(false); - }; - protected Runnable mHideSearchingRunnable = () -> { - mIsSearchingHidden = true; - mInternetDialogSubTitle.setText(getSubtitleText()); - }; @Inject public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, @@ -285,8 +277,6 @@ public class InternetDialog extends SystemUIDialog implements if (DEBUG) { Log.d(TAG, "onStop"); } - mHandler.removeCallbacks(mHideProgressBarRunnable); - mHandler.removeCallbacks(mHideSearchingRunnable); mMobileNetworkLayout.setOnClickListener(null); mConnectedWifListLayout.setOnClickListener(null); if (mSecondaryMobileNetworkLayout != null) { @@ -335,7 +325,6 @@ public class InternetDialog extends SystemUIDialog implements return; } - showProgressBar(); final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked(); final boolean isWifiEnabled = mInternetDialogController.isWifiEnabled(); final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled(); @@ -641,8 +630,7 @@ public class InternetDialog extends SystemUIDialog implements @Nullable CharSequence getSubtitleText() { - return mInternetDialogController.getSubtitleText( - mIsProgressBarVisible && !mIsSearchingHidden); + return mInternetDialogController.getSubtitleText(mIsProgressBarVisible); } private Drawable getSignalStrengthDrawable(int subId) { @@ -657,20 +645,6 @@ public class InternetDialog extends SystemUIDialog implements return mInternetDialogController.getMobileNetworkSummary(subId); } - protected void showProgressBar() { - if (!mInternetDialogController.isWifiEnabled() - || mInternetDialogController.isDeviceLocked()) { - setProgressBarVisible(false); - return; - } - setProgressBarVisible(true); - if (mConnectedWifiEntry != null || mWifiEntriesCount > 0) { - mHandler.postDelayed(mHideProgressBarRunnable, PROGRESS_DELAY_MS); - } else if (!mIsSearchingHidden) { - mHandler.postDelayed(mHideSearchingRunnable, PROGRESS_DELAY_MS); - } - } - private void setProgressBarVisible(boolean visible) { if (mIsProgressBarVisible == visible) { return; @@ -823,6 +797,11 @@ public class InternetDialog extends SystemUIDialog implements } @Override + public void onWifiScan(boolean isScan) { + setProgressBarVisible(isScan); + } + + @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (mAlertDialog != null && !mAlertDialog.isShowing()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index f516f5521d25..592cb3b18e80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -75,7 +75,6 @@ import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.settingslib.wifi.WifiUtils; import com.android.settingslib.wifi.dpp.WifiDppIntentHelper; -import com.android.systemui.res.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -84,6 +83,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.LocationController; @@ -1129,6 +1129,15 @@ public class InternetDialogController implements AccessPointController.AccessPoi public void onSettingsActivityTriggered(Intent settingsIntent) { } + @Override + public void onWifiScan(boolean isScan) { + if (!isWifiEnabled() || isDeviceLocked()) { + mCallback.onWifiScan(false); + return; + } + mCallback.onWifiScan(isScan); + } + private class InternetTelephonyCallback extends TelephonyCallback implements TelephonyCallback.DataConnectionStateListener, TelephonyCallback.DisplayInfoListener, @@ -1372,6 +1381,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, @Nullable WifiEntry connectedEntry, boolean hasMoreWifiEntries); + + void onWifiScan(boolean isScan); } void makeOverlayToast(int stringId) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt index cfb544226c83..9b8dba166274 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.airplane.domain import android.content.res.Resources +import android.content.res.Resources.Theme import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper @@ -27,18 +28,25 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [AirplaneModeTileModel] to [QSTileState]. */ -class AirplaneModeMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<AirplaneModeTileModel> { +class AirplaneModeMapper +@Inject +constructor( + @Main private val resources: Resources, + val theme: Theme, +) : QSTileDataToStateMapper<AirplaneModeTileModel> { override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { val icon = - Icon.Resource( - if (data.isEnabled) { - R.drawable.qs_airplane_icon_on - } else { - R.drawable.qs_airplane_icon_off - }, + Icon.Loaded( + resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_airplane_icon_on + } else { + R.drawable.qs_airplane_icon_off + }, + theme, + ), contentDescription = null ) this.icon = { icon } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt index 63865777e14f..e075e76595d4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.alarm.domain import android.content.res.Resources +import android.content.res.Resources.Theme import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel @@ -30,14 +31,18 @@ import java.util.TimeZone import javax.inject.Inject /** Maps [AlarmTileModel] to [QSTileState]. */ -class AlarmTileMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<AlarmTileModel> { +class AlarmTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<AlarmTileModel> { companion object { val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a") val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm") } override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { when (data) { is AlarmTileModel.NextAlarmSet -> { activationState = QSTileState.ActivationState.ACTIVE diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt index 881a6bd156d2..1b3b5848a7ce 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.flashlight.domain import android.content.res.Resources +import android.content.res.Resources.Theme import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper @@ -27,18 +28,25 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [FlashlightTileModel] to [QSTileState]. */ -class FlashlightMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<FlashlightTileModel> { +class FlashlightMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<FlashlightTileModel> { override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { val icon = - Icon.Resource( - if (data.isEnabled) { - R.drawable.qs_flashlight_icon_on - } else { - R.drawable.qs_flashlight_icon_off - }, + Icon.Loaded( + resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_flashlight_icon_on + } else { + R.drawable.qs_flashlight_icon_off + }, + theme, + ), contentDescription = null ) this.icon = { icon } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt index 7e7034d65efd..fe5445d00670 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.location.domain import android.content.res.Resources +import android.content.res.Resources.Theme import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper @@ -27,18 +28,25 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [LocationTileModel] to [QSTileState]. */ -class LocationTileMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<LocationTileModel> { +class LocationTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<LocationTileModel> { override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { val icon = - Icon.Resource( - if (data.isEnabled) { - R.drawable.qs_location_icon_on - } else { - R.drawable.qs_location_icon_off - }, + Icon.Loaded( + resources.getDrawable( + if (data.isEnabled) { + R.drawable.qs_location_icon_on + } else { + R.drawable.qs_location_icon_off + }, + theme, + ), contentDescription = null ) this.icon = { icon } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt index 25b09131522b..df25600228a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt @@ -27,20 +27,28 @@ import com.android.systemui.res.R import javax.inject.Inject /** Maps [DataSaverTileModel] to [QSTileState]. */ -class DataSaverTileMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<DataSaverTileModel> { +class DataSaverTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, +) : QSTileDataToStateMapper<DataSaverTileModel> { override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState = - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { with(data) { + val iconRes: Int if (isEnabled) { activationState = QSTileState.ActivationState.ACTIVE - icon = { Icon.Resource(R.drawable.qs_data_saver_icon_on, null) } + iconRes = R.drawable.qs_data_saver_icon_on secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2] } else { activationState = QSTileState.ActivationState.INACTIVE - icon = { Icon.Resource(R.drawable.qs_data_saver_icon_off, null) } + iconRes = R.drawable.qs_data_saver_icon_off secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1] } + val loadedIcon = + Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null) + icon = { loadedIcon } contentDescription = label supportedActions = setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt index 3f30c75a6b6a..ffef2b6ecfb5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.impl.uimodenight.domain import android.app.UiModeManager import android.content.res.Resources +import android.content.res.Resources.Theme import android.text.TextUtils import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Main @@ -31,15 +32,19 @@ import java.time.format.DateTimeFormatter import javax.inject.Inject /** Maps [UiModeNightTileModel] to [QSTileState]. */ -class UiModeNightTileMapper @Inject constructor(@Main private val resources: Resources) : - QSTileDataToStateMapper<UiModeNightTileModel> { +class UiModeNightTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Theme, +) : QSTileDataToStateMapper<UiModeNightTileModel> { companion object { val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a") val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") } override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState = with(data) { - QSTileState.build(resources, config.uiConfig) { + QSTileState.build(resources, theme, config.uiConfig) { var shouldSetSecondaryLabel = false if (isPowerSave) { @@ -116,8 +121,9 @@ class UiModeNightTileMapper @Inject constructor(@Main private val resources: Res if (activationState == QSTileState.ActivationState.ACTIVE) R.drawable.qs_light_dark_theme_icon_on else R.drawable.qs_light_dark_theme_icon_off - val iconResource = Icon.Resource(iconRes, null) - icon = { iconResource } + val loadedIcon = + Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null) + icon = { loadedIcon } supportedActions = if (activationState == QSTileState.ActivationState.UNAVAILABLE) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index 23e0cb66bb6a..be1b7404314f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.viewmodel import android.content.res.Resources +import android.content.res.Resources.Theme import android.service.quicksettings.Tile import android.view.View import android.widget.Switch @@ -47,14 +48,17 @@ data class QSTileState( fun build( resources: Resources, + theme: Theme, config: QSTileUIConfig, build: Builder.() -> Unit - ): QSTileState = - build( - { Icon.Resource(config.iconRes, null) }, + ): QSTileState { + val iconDrawable = resources.getDrawable(config.iconRes, theme) + return build( + { Icon.Loaded(iconDrawable, null) }, resources.getString(config.labelRes), build, ) + } fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState = Builder(icon, label).apply(build).build() diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 8a93ef65b4bf..d3459b109d79 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -32,6 +32,7 @@ import javax.inject.Inject * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion. */ @SysUISingleton +@Deprecated("Use ShadeInteractor instead") class ShadeExpansionStateManager @Inject constructor() { private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>() @@ -49,6 +50,7 @@ class ShadeExpansionStateManager @Inject constructor() { * * @see #addExpansionListener */ + @Deprecated("Use ShadeInteractor instead") fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent { expansionListeners.add(listener) return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount) @@ -60,6 +62,7 @@ class ShadeExpansionStateManager @Inject constructor() { } /** Adds a listener that will be notified when the panel state has changed. */ + @Deprecated("Use ShadeInteractor instead") fun addStateListener(listener: ShadeStateListener) { stateListeners.add(listener) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt index 490994d805fe..fc474d2a1307 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt @@ -89,5 +89,12 @@ interface AccessPointController { * "wifi_start_connect_ssid" set as an extra */ fun onSettingsActivityTriggered(settingsIntent: Intent?) + + /** + * Called whenever a Wi-Fi scan is triggered. + * + * @param isScan Whether Wi-Fi scan is triggered or not. + */ + fun onWifiScan(isScan: Boolean) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java index 91ca148c93c9..3a31851bcb4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java @@ -213,6 +213,12 @@ public class AccessPointControllerImpl implements AccessPointController, } } + private void fireWifiScanCallback(boolean isScan) { + for (AccessPointCallback callback : mCallbacks) { + callback.onWifiScan(isScan); + } + } + void dump(PrintWriter pw) { IndentingPrintWriter ipw = new IndentingPrintWriter(pw); ipw.println("AccessPointControllerImpl:"); @@ -240,6 +246,14 @@ public class AccessPointControllerImpl implements AccessPointController, } @Override + public void onWifiEntriesChanged(@WifiPickerTracker.WifiEntriesChangedReason int reason) { + onWifiEntriesChanged(); + if (reason == WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS) { + fireWifiScanCallback(false /* isScan */); + } + } + + @Override public void onNumSavedNetworksChanged() { // Do nothing } @@ -249,6 +263,11 @@ public class AccessPointControllerImpl implements AccessPointController, // Do nothing } + @Override + public void onScanRequested() { + fireWifiScanCallback(true /* isScan */); + } + private final WifiEntry.ConnectCallback mConnectCallback = new WifiEntry.ConnectCallback() { @Override public void onConnectResult(int status) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index adf6cca1ac65..625fdc1c12f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt @@ -20,6 +20,8 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.Context import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.res.R import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject @@ -28,6 +30,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -39,7 +42,9 @@ class SharedNotificationContainerInteractor constructor( configurationRepository: ConfigurationRepository, private val context: Context, - private val splitShadeStateController: SplitShadeStateController + private val splitShadeStateController: SplitShadeStateController, + keyguardInteractor: KeyguardInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, ) { private val _topPosition = MutableStateFlow(0f) @@ -75,6 +80,19 @@ constructor( } .distinctUntilChanged() + /** + * The notification shelf can extend over the lock icon area if: + * * UDFPS supported. Ambient indication will always appear below + * * UDFPS not supported and ambient indication not visible, which will appear above lock icon + */ + val useExtraShelfSpace: Flow<Boolean> = + combine( + keyguardInteractor.ambientIndicationVisible, + deviceEntryUdfpsInteractor.isUdfpsSupported, + ) { ambientIndicationVisible, isUdfpsSupported -> + isUdfpsSupported || !ambientIndicationVisible + } + val isSplitShadeEnabled: Flow<Boolean> = configurationBasedDimensions .map { dimens: ConfigurationBasedDimensions -> dimens.useSplitShade } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index af56a3f51281..12927b87630e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -101,12 +101,13 @@ object SharedNotificationContainerBinder { launch { viewModel - .getMaxNotifications { space -> + .getMaxNotifications { space, extraShelfSpace -> + val shelfHeight = controller.getShelfHeight().toFloat() notificationStackSizeCalculator.computeMaxKeyguardNotifications( controller.getView(), space, - 0f, // Vertical space for shelf is already accounted for - controller.getShelfHeight().toFloat(), + if (extraShelfSpace) shelfHeight else 0f, + shelfHeight, ) } .collect { controller.setMaxDisplayedNotifications(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 9594bc3bfd86..eff91e55d9a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -229,7 +229,7 @@ constructor( * When expanding or when the user is interacting with the shade, keep the count stable; do not * emit a value. */ - fun getMaxNotifications(calculateSpace: (Float) -> Int): Flow<Int> { + fun getMaxNotifications(calculateSpace: (Float, Boolean) -> Int): Flow<Int> { val showLimitedNotifications = isOnLockscreenWithoutShade val showUnlimitedNotifications = combine( @@ -245,11 +245,17 @@ constructor( shadeInteractor.isUserInteracting, bounds, interactor.notificationStackChanged.onStart { emit(Unit) }, - ) { showLimitedNotifications, showUnlimitedNotifications, isUserInteracting, bounds, _ - -> + interactor.useExtraShelfSpace, + ) { flows -> + val showLimitedNotifications = flows[0] as Boolean + val showUnlimitedNotifications = flows[1] as Boolean + val isUserInteracting = flows[2] as Boolean + val bounds = flows[3] as NotificationContainerBounds + val useExtraShelfSpace = flows[5] as Boolean + if (!isUserInteracting) { if (showLimitedNotifications) { - emit(calculateSpace(bounds.bottom - bounds.top)) + emit(calculateSpace(bounds.bottom - bounds.top, useExtraShelfSpace)) } else if (showUnlimitedNotifications) { emit(-1) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 687800714e05..459a74c82da4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -184,6 +184,13 @@ class KeyguardRootViewModelTest : SysuiTestCase() { } @Test + fun translationYInitialValueIsZero() = + testScope.runTest { + val translationY by collectLastValue(underTest.translationY) + assertThat(translationY).isEqualTo(0) + } + + @Test fun translationAndScaleFromBurnInNotDozing() = testScope.runTest { val translationX by collectLastValue(underTest.translationX) diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt index 0a8c0ab9817d..e4432f3038bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt @@ -31,18 +31,20 @@ import android.permission.PermissionGroupUsage import android.permission.PermissionManager import android.testing.AndroidTestingRunner import android.view.View +import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.animation.LaunchableView import com.android.systemui.appops.AppOpsController import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -56,12 +58,12 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -83,60 +85,48 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { private val TEST_INTENT = Intent("test_intent_action") } - @Mock - private lateinit var dialog: PrivacyDialogV2 - @Mock - private lateinit var permissionManager: PermissionManager - @Mock - private lateinit var packageManager: PackageManager - @Mock - private lateinit var privacyItemController: PrivacyItemController - @Mock - private lateinit var userTracker: UserTracker - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var privacyLogger: PrivacyLogger - @Mock - private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var appOpsController: AppOpsController + @Mock private lateinit var dialog: PrivacyDialogV2 + @Mock private lateinit var permissionManager: PermissionManager + @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var privacyItemController: PrivacyItemController + @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var privacyLogger: PrivacyLogger + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var appOpsController: AppOpsController @Captor private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialogV2.OnDialogDismissed> - @Captor - private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback> - @Captor - private lateinit var intentCaptor: ArgumentCaptor<Intent> - @Mock - private lateinit var uiEventLogger: UiEventLogger - @Mock - private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Captor private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback> + @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent> + @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator private val backgroundExecutor = FakeExecutor(FakeSystemClock()) private val uiExecutor = FakeExecutor(FakeSystemClock()) private lateinit var controller: PrivacyDialogControllerV2 private var nextUid: Int = 0 - private val dialogProvider = object : PrivacyDialogControllerV2.DialogProvider { - var list: List<PrivacyDialogV2.PrivacyElement>? = null - var manageApp: ((String, Int, Intent) -> Unit)? = null - var closeApp: ((String, Int) -> Unit)? = null - var openPrivacyDashboard: (() -> Unit)? = null - - override fun makeDialog( - context: Context, - list: List<PrivacyDialogV2.PrivacyElement>, - manageApp: (String, Int, Intent) -> Unit, - closeApp: (String, Int) -> Unit, - openPrivacyDashboard: () -> Unit - ): PrivacyDialogV2 { - this.list = list - this.manageApp = manageApp - this.closeApp = closeApp - this.openPrivacyDashboard = openPrivacyDashboard - return dialog + private val dialogProvider = + object : PrivacyDialogControllerV2.DialogProvider { + var list: List<PrivacyDialogV2.PrivacyElement>? = null + var manageApp: ((String, Int, Intent) -> Unit)? = null + var closeApp: ((String, Int) -> Unit)? = null + var openPrivacyDashboard: (() -> Unit)? = null + + override fun makeDialog( + context: Context, + list: List<PrivacyDialogV2.PrivacyElement>, + manageApp: (String, Int, Intent) -> Unit, + closeApp: (String, Int) -> Unit, + openPrivacyDashboard: () -> Unit + ): PrivacyDialogV2 { + this.list = list + this.manageApp = manageApp + this.closeApp = closeApp + this.openPrivacyDashboard = openPrivacyDashboard + return dialog + } } - } @Before fun setUp() { @@ -144,7 +134,8 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { nextUid = 0 setUpDefaultMockResponses() - controller = PrivacyDialogControllerV2( + controller = + PrivacyDialogControllerV2( permissionManager, packageManager, privacyItemController, @@ -158,7 +149,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { uiEventLogger, dialogLaunchAnimator, dialogProvider - ) + ) } @After @@ -197,7 +188,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) backgroundExecutor.runAllReady() verify(packageManager, atLeastOnce()) - .getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) + .getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) } @Test @@ -208,20 +199,25 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { controller.showDialog(context) exhaustExecutors() - verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), anyBoolean()) + verify(dialogLaunchAnimator, never()).show(any(), any(), anyBoolean()) verify(dialog).show() } @Test fun testShowDialogShowsDialogWithView() { - val view = View(context) + val parent = LinearLayout(context) + val view = + object : View(context), LaunchableView { + override fun setShouldBlockVisibilityChanges(block: Boolean) {} + } + parent.addView(view) val usage = createMockPermGroupUsage() `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context, view) exhaustExecutors() - verify(dialogLaunchAnimator).showFromView(dialog, view) + verify(dialogLaunchAnimator).show(eq(dialog), any(), anyBoolean()) verify(dialog, never()).show() } @@ -276,7 +272,8 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testSingleElementInList() { - val usage = createMockPermGroupUsage( + val usage = + createMockPermGroupUsage( packageName = TEST_PACKAGE_NAME, uid = generateUidForUser(USER_ID), permissionGroupName = PERM_CAMERA, @@ -285,7 +282,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { isPhoneCall = false, attributionTag = null, proxyLabel = TEST_PROXY_LABEL - ) + ) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) @@ -304,33 +301,38 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { assertThat(list.get(0).isPhoneCall).isFalse() assertThat(list.get(0).isService).isFalse() assertThat(list.get(0).permGroupName).isEqualTo(PERM_CAMERA) - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() } } private fun isIntentEqual(actual: Intent, expected: Intent): Boolean { return actual.action == expected.action && - actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) == + actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) == expected.getStringExtra(Intent.EXTRA_PACKAGE_NAME) && - actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle == + actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle == expected.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle } @Test fun testTwoElementsDifferentType_sorted() { - val usage_camera = createMockPermGroupUsage( + val usage_camera = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_camera", permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( + ) + val usage_microphone = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_microphone", permissionGroupName = PERM_MICROPHONE - ) - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_microphone, usage_camera) - ) + ) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_microphone, usage_camera)) controller.showDialog(context) exhaustExecutors() @@ -343,17 +345,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testTwoElementsSameType_oneActive() { - val usage_active = createMockPermGroupUsage( - packageName = "${TEST_PACKAGE_NAME}_active", - isActive = true - ) - val usage_recent = createMockPermGroupUsage( - packageName = "${TEST_PACKAGE_NAME}_recent", - isActive = false - ) - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_recent, usage_active) - ) + val usage_active = + createMockPermGroupUsage(packageName = "${TEST_PACKAGE_NAME}_active", isActive = true) + val usage_recent = + createMockPermGroupUsage(packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_recent, usage_active)) controller.showDialog(context) exhaustExecutors() @@ -364,19 +361,20 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testTwoElementsSameType_twoActive() { - val usage_active = createMockPermGroupUsage( + val usage_active = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_active", isActive = true, lastAccessTimeMillis = 0L - ) - val usage_active_moreRecent = createMockPermGroupUsage( + ) + val usage_active_moreRecent = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_active_recent", isActive = true, lastAccessTimeMillis = 1L - ) - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_active, usage_active_moreRecent) - ) + ) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_active, usage_active_moreRecent)) controller.showDialog(context) exhaustExecutors() assertThat(dialogProvider.list).hasSize(2) @@ -386,24 +384,26 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testManyElementsSameType_bothRecent() { - val usage_recent = createMockPermGroupUsage( + val usage_recent = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false, lastAccessTimeMillis = 0L - ) - val usage_moreRecent = createMockPermGroupUsage( + ) + val usage_moreRecent = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_moreRecent", isActive = false, lastAccessTimeMillis = 1L - ) - val usage_mostRecent = createMockPermGroupUsage( + ) + val usage_mostRecent = + createMockPermGroupUsage( packageName = "${TEST_PACKAGE_NAME}_mostRecent", isActive = false, lastAccessTimeMillis = 2L - ) - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_recent, usage_mostRecent, usage_moreRecent) - ) + ) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_recent, usage_mostRecent, usage_moreRecent)) controller.showDialog(context) exhaustExecutors() @@ -414,19 +414,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testMicAndCameraDisabled() { - val usage_camera = createMockPermGroupUsage( - permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( - permissionGroupName = PERM_MICROPHONE - ) - val usage_location = createMockPermGroupUsage( - permissionGroupName = PERM_LOCATION - ) - - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_camera, usage_location, usage_microphone) - ) + val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA) + val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE) + val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION) + + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_camera, usage_location, usage_microphone)) `when`(privacyItemController.micCameraAvailable).thenReturn(false) controller.showDialog(context) @@ -438,45 +431,29 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testLocationDisabled() { - val usage_camera = createMockPermGroupUsage( - permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( - permissionGroupName = PERM_MICROPHONE - ) - val usage_location = createMockPermGroupUsage( - permissionGroupName = PERM_LOCATION - ) - - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_camera, usage_location, usage_microphone) - ) + val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA) + val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE) + val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION) + + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_camera, usage_location, usage_microphone)) `when`(privacyItemController.locationAvailable).thenReturn(false) controller.showDialog(context) exhaustExecutors() assertThat(dialogProvider.list).hasSize(2) - dialogProvider.list?.forEach { - assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION) - } + dialogProvider.list?.forEach { assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION) } } @Test fun testAllIndicatorsAvailable() { - val usage_camera = createMockPermGroupUsage( - permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( - permissionGroupName = PERM_MICROPHONE - ) - val usage_location = createMockPermGroupUsage( - permissionGroupName = PERM_LOCATION - ) - - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_camera, usage_location, usage_microphone) - ) + val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA) + val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE) + val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION) + + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_camera, usage_location, usage_microphone)) `when`(privacyItemController.micCameraAvailable).thenReturn(true) `when`(privacyItemController.locationAvailable).thenReturn(true) @@ -488,19 +465,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testNoIndicatorsAvailable() { - val usage_camera = createMockPermGroupUsage( - permissionGroupName = PERM_CAMERA - ) - val usage_microphone = createMockPermGroupUsage( - permissionGroupName = PERM_MICROPHONE - ) - val usage_location = createMockPermGroupUsage( - permissionGroupName = PERM_LOCATION - ) - - `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( - listOf(usage_camera, usage_location, usage_microphone) - ) + val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA) + val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE) + val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION) + + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_camera, usage_location, usage_microphone)) `when`(privacyItemController.micCameraAvailable).thenReturn(false) `when`(privacyItemController.locationAvailable).thenReturn(false) @@ -512,11 +482,9 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { @Test fun testNotCurrentUser() { - val usage_other = createMockPermGroupUsage( - uid = generateUidForUser(ENT_USER_ID + 1) - ) + val usage_other = createMockPermGroupUsage(uid = generateUidForUser(ENT_USER_ID + 1)) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) - .thenReturn(listOf(usage_other)) + .thenReturn(listOf(usage_other)) controller.showDialog(context) exhaustExecutors() @@ -559,9 +527,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { // Calls happen in val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) - `when`(userTracker.userProfiles).thenReturn(listOf( - UserInfo(ENT_USER_ID, "", 0) - )) + `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(ENT_USER_ID, "", 0))) controller.showDialog(context) exhaustExecutors() @@ -577,8 +543,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { exhaustExecutors() dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT) - verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS, - USER_ID, TEST_PACKAGE_NAME) + verify(uiEventLogger) + .log( + PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS, + USER_ID, + TEST_PACKAGE_NAME + ) } @Test @@ -589,8 +559,12 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { exhaustExecutors() dialogProvider.closeApp?.invoke(TEST_PACKAGE_NAME, USER_ID) - verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP, - USER_ID, TEST_PACKAGE_NAME) + verify(uiEventLogger) + .log( + PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP, + USER_ID, + TEST_PACKAGE_NAME + ) } @Test @@ -629,9 +603,13 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @@ -648,45 +626,58 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent( - TEST_PACKAGE_NAME, ENT_USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent( + TEST_PACKAGE_NAME, + ENT_USER_ID + ) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @Test fun testDefaultIntentOnInvalidAttributionTag() { - val usage = createMockPermGroupUsage( + val usage = + createMockPermGroupUsage( attributionTag = "INVALID_ATTRIBUTION_TAG", proxyLabel = TEST_PROXY_LABEL - ) + ) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @Test fun testServiceIntentOnCorrectSubAttribution() { - val usage = createMockPermGroupUsage( + val usage = + createMockPermGroupUsage( attributionTag = TEST_ATTRIBUTION_TAG, attributionLabel = "TEST_LABEL" - ) + ) val activityInfo = createMockActivityInfo() val resolveInfo = createMockResolveInfo(activityInfo) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) - `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())) - .thenAnswer { resolveInfo } + `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer { + resolveInfo + } controller.showDialog(context) exhaustExecutors() @@ -694,57 +685,61 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { val navigationIntent = list.get(0).navigationIntent!! assertThat(navigationIntent.action).isEqualTo(Intent.ACTION_MANAGE_PERMISSION_USAGE) assertThat(navigationIntent.getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME)) - .isEqualTo(PERM_CAMERA) + .isEqualTo(PERM_CAMERA) assertThat(navigationIntent.getStringArrayExtra(Intent.EXTRA_ATTRIBUTION_TAGS)) - .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString())) + .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString())) assertThat(navigationIntent.getBooleanExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, false)) - .isTrue() + .isTrue() assertThat(list.get(0).isService).isTrue() } } @Test fun testDefaultIntentOnMissingAttributionLabel() { - val usage = createMockPermGroupUsage( - attributionTag = TEST_ATTRIBUTION_TAG - ) + val usage = createMockPermGroupUsage(attributionTag = TEST_ATTRIBUTION_TAG) val activityInfo = createMockActivityInfo() val resolveInfo = createMockResolveInfo(activityInfo) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) - `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())) - .thenAnswer { resolveInfo } + `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer { + resolveInfo + } controller.showDialog(context) exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @Test fun testDefaultIntentOnIncorrectPermission() { - val usage = createMockPermGroupUsage( - attributionTag = TEST_ATTRIBUTION_TAG - ) + val usage = createMockPermGroupUsage(attributionTag = TEST_ATTRIBUTION_TAG) - val activityInfo = createMockActivityInfo( - permission = "INCORRECT_PERMISSION" - ) + val activityInfo = createMockActivityInfo(permission = "INCORRECT_PERMISSION") val resolveInfo = createMockResolveInfo(activityInfo) `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) - `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())) - .thenAnswer { resolveInfo } + `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer { + resolveInfo + } controller.showDialog(context) exhaustExecutors() dialogProvider.list?.let { list -> - assertThat(isIntentEqual(list.get(0).navigationIntent!!, - controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID))) - .isTrue() + assertThat( + isIntentEqual( + list.get(0).navigationIntent!!, + controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID) + ) + ) + .isTrue() assertThat(list.get(0).isService).isFalse() } } @@ -758,15 +753,18 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { `when`(appOpsController.isMicMuted).thenReturn(false) `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) - .thenAnswer { FakeApplicationInfo(it.getArgument(0)) } + .thenAnswer { FakeApplicationInfo(it.getArgument(0)) } `when`(privacyItemController.locationAvailable).thenReturn(true) `when`(privacyItemController.micCameraAvailable).thenReturn(true) - `when`(userTracker.userProfiles).thenReturn(listOf( - UserInfo(USER_ID, "", 0), - UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE) - )) + `when`(userTracker.userProfiles) + .thenReturn( + listOf( + UserInfo(USER_ID, "", 0), + UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE) + ) + ) `when`(keyguardStateController.isUnlocked).thenReturn(true) } @@ -781,9 +779,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { return user * UserHandle.PER_USER_RANGE + nextUid++ } - private fun createMockResolveInfo( - activityInfo: ActivityInfo? = null - ): ResolveInfo { + private fun createMockResolveInfo(activityInfo: ActivityInfo? = null): ResolveInfo { val resolveInfo = mock(ResolveInfo::class.java) resolveInfo.activityInfo = activityInfo return resolveInfo @@ -822,4 +818,4 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() { `when`(usage.proxyLabel).thenReturn(proxyLabel) return usage } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 6dc7a064af15..b24b8773d600 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -63,13 +63,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.settingslib.wifi.WifiUtils; import com.android.settingslib.wifi.dpp.WifiDppIntentHelper; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.res.R; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.LocationController; @@ -738,6 +738,44 @@ public class InternetDialogControllerTest extends SysuiTestCase { } @Test + public void onWifiScan_isWifiEnabledFalse_callbackOnWifiScanFalse() { + reset(mInternetDialogCallback); + when(mWifiStateWorker.isWifiEnabled()).thenReturn(false); + + mInternetDialogController.onWifiScan(true); + + verify(mInternetDialogCallback).onWifiScan(false); + } + + @Test + public void onWifiScan_isDeviceLockedTrue_callbackOnWifiScanFalse() { + reset(mInternetDialogCallback); + when(mKeyguardStateController.isUnlocked()).thenReturn(false); + + mInternetDialogController.onWifiScan(true); + + verify(mInternetDialogCallback).onWifiScan(false); + } + + @Test + public void onWifiScan_onWifiScanFalse_callbackOnWifiScanFalse() { + reset(mInternetDialogCallback); + + mInternetDialogController.onWifiScan(false); + + verify(mInternetDialogCallback).onWifiScan(false); + } + + @Test + public void onWifiScan_onWifiScanTrue_callbackOnWifiScanTrue() { + reset(mInternetDialogCallback); + + mInternetDialogController.onWifiScan(true); + + verify(mInternetDialogCallback).onWifiScan(true); + } + + @Test public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() { when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID)) .thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index 039e58a64eb5..916bb79d97b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -6,12 +6,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,9 +31,9 @@ import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -48,7 +46,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; @@ -613,66 +610,21 @@ public class InternetDialogTest extends SysuiTestCase { } @Test - public void showProgressBar_wifiDisabled_hideProgressBar() { - Mockito.reset(mHandler); - when(mInternetDialogController.isWifiEnabled()).thenReturn(false); - - mInternetDialog.showProgressBar(); - - assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); - verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong()); - } - - @Test - public void showProgressBar_deviceLocked_hideProgressBar() { - Mockito.reset(mHandler); - when(mInternetDialogController.isDeviceLocked()).thenReturn(true); - - mInternetDialog.showProgressBar(); - - assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); - verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong()); - } - - @Test - public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() { - Mockito.reset(mHandler); - when(mInternetDialogController.isWifiEnabled()).thenReturn(true); + public void onWifiScan_isScanTrue_setProgressBarVisibleTrue() { + mInternetDialog.mIsProgressBarVisible = false; - mInternetDialog.showProgressBar(); + mInternetDialog.onWifiScan(true); - // Show progress bar assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); - - ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); - verify(mHandler).postDelayed(runnableCaptor.capture(), - eq(InternetDialog.PROGRESS_DELAY_MS)); - runnableCaptor.getValue().run(); - - // Then hide progress bar - assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); } @Test - public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() { - Mockito.reset(mHandler); - when(mInternetDialogController.isWifiEnabled()).thenReturn(true); - mInternetDialog.mConnectedWifiEntry = null; - mInternetDialog.mWifiEntriesCount = 0; + public void onWifiScan_isScanFalse_setProgressBarVisibleFalse() { + mInternetDialog.mIsProgressBarVisible = true; - mInternetDialog.showProgressBar(); - - // Show progress bar - assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); + mInternetDialog.onWifiScan(false); - ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); - verify(mHandler).postDelayed(runnableCaptor.capture(), - eq(InternetDialog.PROGRESS_DELAY_MS)); - runnableCaptor.getValue().run(); - - // Then hide searching sub-title only - assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); - assertThat(mInternetDialog.mIsSearchingHidden).isTrue(); + assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 657f9127dc7e..e572dcca5a34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -23,6 +23,8 @@ import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; @@ -37,8 +39,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; - import android.annotation.IdRes; import android.content.ContentResolver; import android.content.res.Configuration; @@ -86,6 +86,7 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.view.LongPressHandlingView; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; @@ -402,6 +403,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( StateFlowKt.MutableStateFlow(false)); + DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = + mock(DeviceEntryUdfpsInteractor.class); + when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow()); + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), new FakeDeviceProvisioningRepository(), @@ -418,7 +423,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { new SharedNotificationContainerInteractor( new FakeConfigurationRepository(), mContext, - new ResourcesSplitShadeStateController() + new ResourcesSplitShadeStateController(), + mKeyguardInteractor, + deviceEntryUdfpsInteractor ), mShadeRepository ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 5ffbe65d2c50..9d8b21464585 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -23,6 +23,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -54,6 +56,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -235,6 +238,11 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mKeyguardSecurityModel, mSelectedUserInteractor, powerInteractor); + + DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = + mock(DeviceEntryUdfpsInteractor.class); + when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow()); + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), new FakeDeviceProvisioningRepository(), @@ -251,7 +259,9 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - new ResourcesSplitShadeStateController()), + new ResourcesSplitShadeStateController(), + keyguardInteractor, + deviceEntryUdfpsInteractor), shadeRepository ) ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index e723d7d0367b..eb5633b70f61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; import android.content.res.Resources; @@ -41,6 +42,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.FeatureFlags; @@ -275,6 +277,10 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); + DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = + mock(DeviceEntryUdfpsInteractor.class); + when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow()); + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), deviceProvisioningRepository, @@ -291,7 +297,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - splitShadeStateController), + splitShadeStateController, + keyguardInteractor, + deviceEntryUdfpsInteractor), mShadeRepository ) ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index dff91ddf559f..f25ce0aa5278 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -53,6 +54,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSe import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.flow.emptyFlow import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -83,6 +85,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { FromPrimaryBouncerTransitionInteractor @Mock lateinit var interactionJankMonitor: InteractionJankMonitor @Mock lateinit var mockDarkAnimator: ObjectAnimator + @Mock lateinit var deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor private lateinit var controller: StatusBarStateControllerImpl private lateinit var uiEventLogger: UiEventLoggerFake @@ -164,6 +167,8 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { mock(), powerInteractor ) + + whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow()) shadeInteractor = ShadeInteractorImpl( testScope.backgroundScope, @@ -181,7 +186,9 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { SharedNotificationContainerInteractor( configurationRepository, mContext, - ResourcesSplitShadeStateController() + ResourcesSplitShadeStateController(), + keyguardInteractor, + deviceEntryUdfpsInteractor, ), shadeRepository, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt index 3fef1d9832f0..5bc75e8b84c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt @@ -148,6 +148,24 @@ class AccessPointControllerImplTest : SysuiTestCase() { } @Test + fun onWifiEntriesChanged_reasonIsScanResults_fireWifiScanCallbackFalse() { + controller.addAccessPointCallback(callback) + + controller.onWifiEntriesChanged(WifiPickerTracker.WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS) + + verify(callback).onWifiScan(false) + } + + @Test + fun onScanRequested_fireWifiScanCallbackTrue() { + controller.addAccessPointCallback(callback) + + controller.onScanRequested() + + verify(callback).onWifiScan(true) + } + + @Test fun testOnNumSavedNetworksChangedDoesntTriggerCallback() { controller.addAccessPointCallback(callback) controller.onNumSavedNetworksChanged() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index bfa03eed57a0..8cf64a5aa8fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -48,7 +48,6 @@ import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -56,6 +55,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; +import com.android.systemui.log.LogAssertKt; import com.android.systemui.statusbar.NotificationInteractionTracker; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.NotifPipelineFlags; @@ -76,6 +76,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; import com.android.systemui.util.time.FakeSystemClock; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -129,10 +130,6 @@ public class ShadeListBuilderTest extends SysuiTestCase { private Map<String, Integer> mNextIdMap = new ArrayMap<>(); private int mNextRank = 0; - private Log.TerribleFailureHandler mOldWtfHandler = null; - private Log.TerribleFailure mLastWtf = null; - private int mWtfCount = 0; - @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -1756,20 +1753,19 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPreGroupFilter(filter); mListBuilder.addOnBeforeTransformGroupsListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the filter is invalidated exactly // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); // THEN an exception is NOT thrown directly, but a WTF IS logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } - @Test(expected = IllegalStateException.class) + @Test public void testOutOfOrderPreGroupFilterInvalidationThrowsAfterTooManyRuns() { // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage, NotifFilter filter = new PackageFilter(PACKAGE_1); @@ -1778,20 +1774,20 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPreGroupFilter(filter); mListBuilder.addOnBeforeTransformGroupsListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the filter is invalidated more than // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception IS thrown. + addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - dispatchBuild(); - try { - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - } finally { - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - } - // THEN an exception IS thrown. + LogAssertKt.assertLogsWtfs(() -> { + Assert.assertThrows(IllegalStateException.class, () -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); + }); } @Test @@ -1803,26 +1799,30 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPreGroupFilter(filter); mListBuilder.addOnBeforeTransformGroupsListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the filter is invalidated // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, the pipeline runs for a non-reentrant reason, // and then the filter is invalidated MAX_CONSECUTIVE_REENTRANT_REBUILDS times again, + + // THEN an exception is NOT thrown, but WTFs ARE logged. + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - // Note: dispatchBuild itself triggers a non-reentrant pipeline run. - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); - // THEN an exception is NOT thrown, but WTFs ARE logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS * 2); + invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + LogAssertKt.assertLogsWtfs(() -> { + // Note: dispatchBuild itself triggers a non-reentrant pipeline run. + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } @Test - public void testOutOfOrderPrompterInvalidationDoesNotThrowBeforeTooManyRuns() { + public void testOutOfOrderPromoterInvalidationDoesNotThrowBeforeTooManyRuns() { // GIVEN a NotifPromoter that gets invalidated during the sorting stage, NotifPromoter promoter = new IdPromoter(47); CountingInvalidator invalidator = new CountingInvalidator(promoter); @@ -1830,22 +1830,22 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPromoter(promoter); mListBuilder.addOnBeforeSortListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the promoter is invalidated exactly // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, - addNotif(0, PACKAGE_1); - invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); // THEN an exception is NOT thrown directly, but a WTF IS logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + addNotif(0, PACKAGE_1); + invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } - @Test(expected = IllegalStateException.class) - public void testOutOfOrderPrompterInvalidationThrowsAfterTooManyRuns() { + @Test + public void testOutOfOrderPromoterInvalidationThrowsAfterTooManyRuns() { // GIVEN a NotifPromoter that gets invalidated during the sorting stage, NotifPromoter promoter = new IdPromoter(47); CountingInvalidator invalidator = new CountingInvalidator(promoter); @@ -1853,20 +1853,20 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addPromoter(promoter); mListBuilder.addOnBeforeSortListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the promoter is invalidated more than // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception IS thrown. + addNotif(0, PACKAGE_1); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - dispatchBuild(); - try { - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - } finally { - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - } - // THEN an exception IS thrown. + LogAssertKt.assertLogsWtfs(() -> { + Assert.assertThrows(IllegalStateException.class, () -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); + }); } @Test @@ -1878,20 +1878,21 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.setComparators(singletonList(comparator)); mListBuilder.addOnBeforeRenderListListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the comparator is invalidated exactly // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception is NOT thrown directly, but a WTF IS logged. + addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception is NOT thrown directly, but a WTF IS logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } - @Test(expected = IllegalStateException.class) + @Test public void testOutOfOrderComparatorInvalidationThrowsAfterTooManyRuns() { // GIVEN a NotifComparator that gets invalidated during the finalizing stage, NotifComparator comparator = new HypeComparator(PACKAGE_1); @@ -1900,16 +1901,20 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.setComparators(singletonList(comparator)); mListBuilder.addOnBeforeRenderListListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the comparator is invalidated more than // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception IS thrown. + addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception IS thrown. + LogAssertKt.assertLogsWtfs(() -> { + Assert.assertThrows(IllegalStateException.class, () -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); + }); } @Test @@ -1921,20 +1926,21 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addFinalizeFilter(filter); mListBuilder.addOnBeforeRenderListListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, + + // THEN an exception is NOT thrown directly, but a WTF IS logged. + addNotif(0, PACKAGE_2); invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - dispatchBuild(); - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - // THEN an exception is NOT thrown directly, but a WTF IS logged. - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); + LogAssertKt.assertLogsWtfs(() -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); } - @Test(expected = IllegalStateException.class) + @Test public void testOutOfOrderPreRenderFilterInvalidationThrowsAfterTooManyRuns() { // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage, NotifFilter filter = new PackageFilter(PACKAGE_1); @@ -1943,59 +1949,22 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.addFinalizeFilter(filter); mListBuilder.addOnBeforeRenderListListener(listener); - interceptWtfs(); - // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, - addNotif(0, PACKAGE_2); - invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - dispatchBuild(); - try { - runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); - } finally { - expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS); - } // THEN an exception IS thrown. - } - - private void interceptWtfs() { - assertNull(mOldWtfHandler); - mLastWtf = null; - mWtfCount = 0; + addNotif(0, PACKAGE_2); + invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); - mOldWtfHandler = Log.setWtfHandler((tag, e, system) -> { - Log.e("ShadeListBuilderTest", "Observed WTF: " + e); - mLastWtf = e; - mWtfCount++; + LogAssertKt.assertLogsWtfs(() -> { + Assert.assertThrows(IllegalStateException.class, () -> { + dispatchBuild(); + runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + }); }); } - private void expectNoWtfs() { - assertNull(expectWtfs(0)); - } - - private Log.TerribleFailure expectWtf() { - return expectWtfs(1); - } - - private Log.TerribleFailure expectWtfs(int expectedWtfCount) { - assertNotNull(mOldWtfHandler); - - Log.setWtfHandler(mOldWtfHandler); - mOldWtfHandler = null; - - Log.TerribleFailure wtf = mLastWtf; - int wtfCount = mWtfCount; - - mLastWtf = null; - mWtfCount = 0; - - assertEquals(expectedWtfCount, wtfCount); - return wtf; - } - @Test public void testStableOrdering() { mStabilityManager.setAllowEntryReordering(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt index a07b5705d171..327a07d6179f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt @@ -20,57 +20,86 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SharedNotificationContainerInteractorTest : SysuiTestCase() { - private lateinit var configurationRepository: FakeConfigurationRepository - private lateinit var underTest: SharedNotificationContainerInteractor - - @Before - fun setUp() { - configurationRepository = FakeConfigurationRepository() - underTest = - SharedNotificationContainerInteractor( - configurationRepository, - mContext, - ResourcesSplitShadeStateController() - ) - } + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val configurationRepository = kosmos.fakeConfigurationRepository + private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + private val underTest = kosmos.sharedNotificationContainerInteractor @Test - fun validateConfigValues() = runTest { - overrideResource(R.bool.config_use_split_notification_shade, true) - overrideResource(R.bool.config_use_large_screen_shade_header, false) - overrideResource(R.dimen.notification_panel_margin_horizontal, 0) - overrideResource(R.dimen.notification_panel_margin_bottom, 10) - overrideResource(R.dimen.notification_panel_margin_top, 10) - overrideResource(R.dimen.large_screen_shade_header_height, 0) - overrideResource(R.dimen.keyguard_split_shade_top_margin, 55) - - val dimens = collectLastValue(underTest.configurationBasedDimensions) - - configurationRepository.onAnyConfigurationChange() - runCurrent() - - val lastDimens = dimens()!! - - assertThat(lastDimens.useSplitShade).isTrue() - assertThat(lastDimens.useLargeScreenHeader).isFalse() - assertThat(lastDimens.marginHorizontal).isEqualTo(0) - assertThat(lastDimens.marginBottom).isGreaterThan(0) - assertThat(lastDimens.marginTop).isGreaterThan(0) - assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0) - assertThat(lastDimens.keyguardSplitShadeTopMargin).isEqualTo(55) - } + fun validateConfigValues() = + testScope.runTest { + overrideResource(R.bool.config_use_split_notification_shade, true) + overrideResource(R.bool.config_use_large_screen_shade_header, false) + overrideResource(R.dimen.notification_panel_margin_horizontal, 0) + overrideResource(R.dimen.notification_panel_margin_bottom, 10) + overrideResource(R.dimen.notification_panel_margin_top, 10) + overrideResource(R.dimen.large_screen_shade_header_height, 0) + overrideResource(R.dimen.keyguard_split_shade_top_margin, 55) + + val dimens = collectLastValue(underTest.configurationBasedDimensions) + + configurationRepository.onAnyConfigurationChange() + runCurrent() + + val lastDimens = dimens()!! + + assertThat(lastDimens.useSplitShade).isTrue() + assertThat(lastDimens.useLargeScreenHeader).isFalse() + assertThat(lastDimens.marginHorizontal).isEqualTo(0) + assertThat(lastDimens.marginBottom).isGreaterThan(0) + assertThat(lastDimens.marginTop).isGreaterThan(0) + assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0) + assertThat(lastDimens.keyguardSplitShadeTopMargin).isEqualTo(55) + } + + @Test + fun useExtraShelfSpaceIsTrueWithUdfps() = + testScope.runTest { + val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace) + + keyguardRepository.ambientIndicationVisible.value = true + fingerprintPropertyRepository.supportsUdfps() + + assertThat(useExtraShelfSpace).isEqualTo(true) + } + + @Test + fun useExtraShelfSpaceIsTrueWithRearFpsAndNoAmbientIndicationArea() = + testScope.runTest { + val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace) + + keyguardRepository.ambientIndicationVisible.value = false + fingerprintPropertyRepository.supportsRearFps() + + assertThat(useExtraShelfSpace).isEqualTo(true) + } + + @Test + fun useExtraShelfSpaceIsFalseWithRearFpsAndAmbientIndicationArea() = + testScope.runTest { + val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace) + + keyguardRepository.ambientIndicationVisible.value = true + fingerprintPropertyRepository.supportsRearFps() + + assertThat(useExtraShelfSpace).isEqualTo(false) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index f0205b3f5974..36a471238c8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -332,8 +332,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { fun maxNotificationsOnLockscreen() = testScope.runTest { var notificationCount = 10 - val maxNotifications by - collectLastValue(underTest.getMaxNotifications { notificationCount }) + val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount } + val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) showLockscreen() @@ -355,8 +355,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() = testScope.runTest { var notificationCount = 10 - val maxNotifications by - collectLastValue(underTest.getMaxNotifications { notificationCount }) + val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount } + val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) showLockscreen() @@ -390,7 +390,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun maxNotificationsOnShade() = testScope.runTest { - val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 }) + val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 } + val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) // Show lockscreen with shade expanded showLockscreenWithShadeExpanded() diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 5b9b390eea2d..b217195000b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -29,6 +29,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -97,6 +99,7 @@ import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepositor import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FakeFeatureFlagsClassic; @@ -465,6 +468,10 @@ public class BubblesTest extends SysuiTestCase { ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); + DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = + mock(DeviceEntryUdfpsInteractor.class); + when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow()); + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), @@ -481,7 +488,9 @@ public class BubblesTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - splitShadeStateController), + splitShadeStateController, + keyguardInteractor, + deviceEntryUdfpsInteractor), shadeRepository ) ); diff --git a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt new file mode 100644 index 000000000000..b88f302cdfdd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt @@ -0,0 +1,35 @@ +/* + * 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.graphics.drawable + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.PixelFormat + +/** + * Stub drawable that does nothing. It's to be used in tests as a mock drawable and checked for the + * same instance + */ +class TestStubDrawable : Drawable() { + + override fun draw(canvas: Canvas) = Unit + override fun setAlpha(alpha: Int) = Unit + override fun setColorFilter(colorFilter: ColorFilter?) = Unit + override fun getOpacity(): Int = PixelFormat.UNKNOWN + + override fun equals(other: Any?): Boolean = this === other +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index 2cb17b5badc4..c85c27e277b4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -1,19 +1,47 @@ package com.android.systemui.communal.data.repository import com.android.systemui.communal.shared.model.CommunalSceneKey +import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.dagger.qualifiers.Background +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.test.TestScope /** Fake implementation of [CommunalRepository]. */ +@OptIn(ExperimentalCoroutinesApi::class) class FakeCommunalRepository( + @Background applicationScope: CoroutineScope = TestScope(), override var isCommunalEnabled: Boolean = false, override val desiredScene: MutableStateFlow<CommunalSceneKey> = - MutableStateFlow(CommunalSceneKey.Blank) + MutableStateFlow(CommunalSceneKey.DEFAULT), ) : CommunalRepository { override fun setDesiredScene(desiredScene: CommunalSceneKey) { this.desiredScene.value = desiredScene } + private val defaultTransitionState = + ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT) + private val _transitionState = MutableStateFlow<Flow<ObservableCommunalTransitionState>?>(null) + override val transitionState: StateFlow<ObservableCommunalTransitionState> = + _transitionState + .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = defaultTransitionState, + ) + + override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) { + _transitionState.value = transitionState + } + fun setIsCommunalEnabled(value: Boolean) { isCommunalEnabled = value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 0e7c6625264c..c5d745a65e96 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -121,6 +121,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null) + override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false) + override fun setQuickSettingsVisible(isVisible: Boolean) { _isQuickSettingsVisible.value = isVisible } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt index 10f9346aba14..6ccb3bc2812e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt @@ -59,6 +59,17 @@ fun assertLogsWtf( ): TerribleFailureLog = assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() } +fun assertLogsWtfs( + message: String = "Expected Log.wtf to be called once or more", + loggingBlock: () -> Unit, +): TerribleFailureLog = assertLogsWtf(message, allowMultiple = true, loggingBlock) + +@JvmOverloads +fun assertLogsWtfs( + message: String = "Expected Log.wtf to be called once or more", + loggingRunnable: Runnable, +): TerribleFailureLog = assertLogsWtfs(message) { loggingRunnable.run() } + /** The data passed to [TerribleFailureHandler.onTerribleFailure] */ data class TerribleFailureLog( val tag: String, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt index 1cb2587e4e99..6332c1a8010d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt @@ -19,9 +19,14 @@ package com.android.systemui.qs import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.InstanceIdSequenceFake import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.qs.tiles.di.NewQSTileFactory val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by Kosmos.Fixture { InstanceIdSequenceFake(0) } val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() } val Kosmos.qsEventLogger: QsEventLoggerFake by Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) } + +var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>() +var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt new file mode 100644 index 000000000000..f01e3aaa7089 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.customTileStatePersister: CustomTileStatePersister by + Kosmos.Fixture { fakeCustomTileStatePersister } +val Kosmos.fakeCustomTileStatePersister by Kosmos.Fixture { FakeCustomTileStatePersister() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt new file mode 100644 index 000000000000..f8ce707b0bb2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +/** Returns mocks */ +var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by + Kosmos.Fixture { TileLifecycleManager.Factory { _, _ -> mock<TileLifecycleManager>() } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt new file mode 100644 index 000000000000..d93dd8d42721 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.data.model + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor + +val Kosmos.workTileRestoreProcessor by Kosmos.Fixture { WorkTileRestoreProcessor() } + +var Kosmos.restoreProcessors by + Kosmos.Fixture { + setOf( + workTileRestoreProcessor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt new file mode 100644 index 000000000000..009148266143 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeTileSpecRepository by Kosmos.Fixture { FakeTileSpecRepository() } +var Kosmos.tileSpecRepository: TileSpecRepository by Kosmos.Fixture { fakeTileSpecRepository } + +val Kosmos.fakeAutoAddRepository by Kosmos.Fixture { FakeAutoAddRepository() } +var Kosmos.autoAddRepository: AutoAddRepository by Kosmos.Fixture { fakeAutoAddRepository } + +val Kosmos.fakeRestoreRepository by Kosmos.Fixture { FakeQSSettingsRestoredRepository() } +var Kosmos.restoreRepository: QSSettingsRestoredRepository by + Kosmos.Fixture { fakeRestoreRepository } + +val Kosmos.fakeInstalledTilesRepository by + Kosmos.Fixture { FakeInstalledTilesComponentRepository() } +var Kosmos.installedTilesRepository: InstalledTilesComponentRepository by + Kosmos.Fixture { fakeInstalledTilesRepository } + +val Kosmos.fakeCustomTileAddedRepository by Kosmos.Fixture { FakeCustomTileAddedRepository() } +var Kosmos.customTileAddedRepository: CustomTileAddedRepository by + Kosmos.Fixture { fakeCustomTileAddedRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt new file mode 100644 index 000000000000..35f178b62a08 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.autoaddable + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor +import com.android.systemui.settings.userTracker + +val Kosmos.workTileAutoAddable by + Kosmos.Fixture { WorkTileAutoAddable(userTracker, workTileRestoreProcessor) } + +var Kosmos.autoAddables by Kosmos.Fixture { setOf(workTileAutoAddable) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt index ebdd6fd7aac0..bf8f4da34d5b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt @@ -39,14 +39,18 @@ class FakeAutoAddable( return getFlow(userId).asStateFlow().filterNotNull() } - suspend fun sendRemoveSignal(userId: Int) { + fun sendRemoveSignal(userId: Int) { getFlow(userId).value = AutoAddSignal.Remove(spec) } - suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) { + fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) { getFlow(userId).value = AutoAddSignal.Add(spec, position) } + fun sendRemoveTrackingSignal(userId: Int) { + getFlow(userId).value = AutoAddSignal.RemoveTracking(spec) + } + override val description: String get() = "FakeAutoAddable($spec)" } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt new file mode 100644 index 000000000000..5e8471c5575b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.interactor + +import com.android.systemui.dump.dumpManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.qs.pipeline.data.repository.autoAddRepository +import com.android.systemui.qs.pipeline.domain.autoaddable.autoAddables +import com.android.systemui.qs.pipeline.shared.logging.qsLogger + +val Kosmos.autoAddInteractor by + Kosmos.Fixture { + AutoAddInteractor( + autoAddables, + autoAddRepository, + dumpManager, + qsLogger, + applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt new file mode 100644 index 000000000000..67df563ec5b0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt @@ -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.systemui.qs.pipeline.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.qs.external.customTileStatePersister +import com.android.systemui.qs.external.tileLifecycleManagerFactory +import com.android.systemui.qs.newQSTileFactory +import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository +import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository +import com.android.systemui.qs.pipeline.shared.logging.qsLogger +import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository +import com.android.systemui.qs.qsTileFactory +import com.android.systemui.settings.userTracker +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.currentTilesInteractor: CurrentTilesInteractor by + Kosmos.Fixture { + CurrentTilesInteractorImpl( + tileSpecRepository, + installedTilesRepository, + userRepository, + customTileStatePersister, + { newQSTileFactory }, + qsTileFactory, + customTileAddedRepository, + tileLifecycleManagerFactory, + userTracker, + testDispatcher, + testDispatcher, + applicationCoroutineScope, + qsLogger, + pipelineFlagsRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt new file mode 100644 index 000000000000..55c23d41e548 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.qs.pipeline.data.model.restoreProcessors +import com.android.systemui.qs.pipeline.data.repository.autoAddRepository +import com.android.systemui.qs.pipeline.data.repository.restoreRepository +import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository +import com.android.systemui.qs.pipeline.shared.logging.qsLogger + +val Kosmos.restoreReconciliationInteractor by + Kosmos.Fixture { + RestoreReconciliationInteractor( + tileSpecRepository, + autoAddRepository, + restoreRepository, + restoreProcessors, + qsLogger, + applicationCoroutineScope, + testDispatcher, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt new file mode 100644 index 000000000000..961545aba4e8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.shared + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.pipelineFlagsRepository by Kosmos.Fixture { QSPipelineFlagsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt new file mode 100644 index 000000000000..7d52f5d8aa34 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.shared.logging + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +/** mock */ +var Kosmos.qsLogger: QSPipelineLogger by Kosmos.Fixture { mock<QSPipelineLogger>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt index 7494ccf32a2c..2ca338a3af9c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt @@ -72,6 +72,7 @@ class FakeUserTracker( onBeforeUserSwitching() onUserChanging() onUserChanged() + onProfileChanged() } fun onBeforeUserSwitching(userId: Int = _userId) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt new file mode 100644 index 000000000000..ffa86ff03ab1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.settings + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeUserTracker by Kosmos.Fixture { FakeUserTracker() } +var Kosmos.userTracker: UserTracker by Kosmos.Fixture { fakeUserTracker } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt index 3403227f6d27..13d577bde711 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.applicationContext import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.policy.splitShadeStateController @@ -27,5 +29,7 @@ val Kosmos.sharedNotificationContainerInteractor by configurationRepository = configurationRepository, context = applicationContext, splitShadeStateController = splitShadeStateController, + keyguardInteractor = keyguardInteractor, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, ) } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index d175713eb92f..513c09587026 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -16,6 +16,8 @@ package android.platform.test.ravenwood; +import static org.junit.Assert.fail; + import android.platform.test.annotations.IgnoreUnderRavenwood; import org.junit.Assume; @@ -36,6 +38,15 @@ public class RavenwoodRule implements TestRule { private static final boolean IS_UNDER_RAVENWOOD = RavenwoodRuleImpl.isUnderRavenwood(); + /** + * When probing is enabled, all tests will be unconditionally run under Ravenwood to detect + * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}. + * + * This is typically helpful for internal maintainers discovering tests that had previously + * been ignored, but now have enough Ravenwood-supported functionality to be enabled. + */ + private static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE + private static final int SYSTEM_UID = 1000; private static final int NOBODY_UID = 9999; private static final int FIRST_APPLICATION_UID = 10000; @@ -97,26 +108,76 @@ public class RavenwoodRule implements TestRule { return IS_UNDER_RAVENWOOD; } + /** + * Test if the given {@link Description} has been marked with an {@link IgnoreUnderRavenwood} + * annotation, either at the method or class level. + */ + private static boolean hasIgnoreUnderRavenwoodAnnotation(Description description) { + if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) { + return true; + } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) { + return true; + } else { + return false; + } + } + @Override public Statement apply(Statement base, Description description) { + if (ENABLE_PROBE_IGNORED) { + return applyProbeIgnored(base, description); + } else { + return applyDefault(base, description); + } + } + + /** + * Run the given {@link Statement} with no special treatment. + */ + private Statement applyDefault(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { - if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) { - Assume.assumeFalse(IS_UNDER_RAVENWOOD); - } - if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) { + if (hasIgnoreUnderRavenwoodAnnotation(description)) { Assume.assumeFalse(IS_UNDER_RAVENWOOD); } - if (IS_UNDER_RAVENWOOD) { - RavenwoodRuleImpl.init(RavenwoodRule.this); - } + + RavenwoodRuleImpl.init(RavenwoodRule.this); try { base.evaluate(); } finally { - if (IS_UNDER_RAVENWOOD) { - RavenwoodRuleImpl.reset(RavenwoodRule.this); + RavenwoodRuleImpl.reset(RavenwoodRule.this); + } + } + }; + } + + /** + * Run the given {@link Statement} with probing enabled. All tests will be unconditionally + * run under Ravenwood to detect cases where a test is able to pass despite being marked as + * {@code IgnoreUnderRavenwood}. + */ + private Statement applyProbeIgnored(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + RavenwoodRuleImpl.init(RavenwoodRule.this); + try { + base.evaluate(); + } catch (Throwable t) { + if (hasIgnoreUnderRavenwoodAnnotation(description)) { + // This failure is expected, so eat the exception and report the + // assumption failure that test authors expect + Assume.assumeFalse(IS_UNDER_RAVENWOOD); } + throw t; + } finally { + RavenwoodRuleImpl.reset(RavenwoodRule.this); + } + + if (hasIgnoreUnderRavenwoodAnnotation(description) && IS_UNDER_RAVENWOOD) { + fail("Test was annotated with IgnoreUnderRavenwood, but it actually " + + "passed under Ravenwood; consider removing the annotation"); } } }; diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java index fb71e9d1ac6f..0ff6a1ad846b 100644 --- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java +++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java @@ -22,12 +22,10 @@ public class RavenwoodRuleImpl { } public static void init(RavenwoodRule rule) { - // Must be provided by impl to reference runtime internals - throw new UnsupportedOperationException(); + // No-op when running on a real device } public static void reset(RavenwoodRule rule) { - // Must be provided by impl to reference runtime internals - throw new UnsupportedOperationException(); + // No-op when running on a real device } } diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index 9fcabd67f9dd..13908f1732e1 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -1,8 +1,16 @@ # Only classes listed here can use the Ravenwood annotations. com.android.internal.util.ArrayUtils +com.android.internal.os.BatteryStatsHistory +com.android.internal.os.BatteryStatsHistory$TraceDelegate +com.android.internal.os.BatteryStatsHistory$VarintParceler +com.android.internal.os.BatteryStatsHistoryIterator +com.android.internal.os.Clock com.android.internal.os.LongArrayMultiStateCounter com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer +com.android.internal.os.MonotonicClock +com.android.internal.os.PowerStats +com.android.internal.os.PowerStats$Descriptor android.util.AtomicFile android.util.DataUnit @@ -25,10 +33,15 @@ android.util.TimeUtils android.util.Xml android.os.BatteryConsumer +android.os.BatteryStats$HistoryItem +android.os.BatteryStats$HistoryStepDetails +android.os.BatteryStats$HistoryTag +android.os.BatteryStats$ProcessStateChange android.os.Binder android.os.Binder$IdentitySupplier android.os.Broadcaster android.os.BundleMerger +android.os.ConditionVariable android.os.FileUtils android.os.FileUtils$MemoryPipe android.os.Handler diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 0696807b3c8c..97d36d443620 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -209,6 +209,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final ComponentName mComponentName; int mGenericMotionEventSources; + int mObservedMotionEventSources; // the events pending events to be dispatched to this service final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>(); @@ -397,6 +398,19 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mNotificationTimeout = info.notificationTimeout; mIsDefault = (info.flags & DEFAULT) != 0; mGenericMotionEventSources = info.getMotionEventSources(); + if (android.view.accessibility.Flags.motionEventObserving()) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING) + == PackageManager.PERMISSION_GRANTED) { + mObservedMotionEventSources = info.getObservedMotionEventSources(); + } else { + Slog.e( + LOG_TAG, + "Observing motion events requires" + + " android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING."); + mObservedMotionEventSources = 0; + } + } if (supportsFlagForNotImportantViews(info)) { if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) { @@ -1599,7 +1613,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int displayId = displays[i].getDisplayId(); onDisplayRemoved(displayId); } - if (Flags.cleanupA11yOverlays()) { + if (com.android.server.accessibility.Flags.cleanupA11yOverlays()) { detachAllOverlays(); } } @@ -1919,6 +1933,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return (mGenericMotionEventSources & eventSourceWithoutClass) != 0; } + /** * Called by the invocation handler to notify the service that the * state of magnification has changed. diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 6cac6a47c77b..9ddc35ae240b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -198,6 +198,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo // State tracking for generic MotionEvents is display-agnostic so we only need one. private GenericMotionEventStreamState mGenericMotionEventStreamState; private int mCombinedGenericMotionEventSources = 0; + private int mCombinedMotionEventObservedSources = 0; private EventStreamState mKeyboardStreamState; @@ -525,16 +526,33 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS) != 0) { - addFirstEventHandler(displayId, new BaseEventStreamTransformation() { - @Override - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { - if (!anyServiceWantsGenericMotionEvent(rawEvent) - || !mAms.sendMotionEventToListeningServices(rawEvent)) { - super.onMotionEvent(event, rawEvent, policyFlags); - } - } - }); + addFirstEventHandler( + displayId, + new BaseEventStreamTransformation() { + @Override + public void onMotionEvent( + MotionEvent event, MotionEvent rawEvent, int policyFlags) { + boolean passAlongEvent = true; + if (anyServiceWantsGenericMotionEvent(event)) { + // Some service wants this event, so try to deliver it to at least + // one service. + if (mAms.sendMotionEventToListeningServices(event)) { + // A service accepted this event, so prevent it from passing + // down the stream by default. + passAlongEvent = false; + } + // However, if a service is observing these events instead of + // consuming them then ensure + // it is always passed along to the next stage of the event stream. + if (anyServiceWantsToObserveMotionEvent(event)) { + passAlongEvent = true; + } + } + if (passAlongEvent) { + super.onMotionEvent(event, rawEvent, policyFlags); + } + } + }); } if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 @@ -542,15 +560,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0) || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { final MagnificationGestureHandler magnificationGestureHandler = - createMagnificationGestureHandler(displayId, - displayContext); + createMagnificationGestureHandler(displayId, displayContext); addFirstEventHandler(displayId, magnificationGestureHandler); mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); } if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { - MotionEventInjector injector = new MotionEventInjector( - mContext.getMainLooper(), mAms.getTraceManager()); + MotionEventInjector injector = + new MotionEventInjector(mContext.getMainLooper(), mAms.getTraceManager()); addFirstEventHandler(displayId, injector); mMotionEventInjectors.put(displayId, injector); } @@ -923,6 +940,20 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } } + private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) { + // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing + // touch exploration. + if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN) + && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { + return false; + } + final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; + return (mCombinedGenericMotionEventSources + & mCombinedMotionEventObservedSources + & eventSourceWithoutClass) + != 0; + } + private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) { // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing // touch exploration. @@ -938,6 +969,10 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mCombinedGenericMotionEventSources = sources; } + public void setCombinedMotionEventObservedSources(int sources) { + mCombinedMotionEventObservedSources = sources; + } + /** * Keeps state of streams of events from all keyboard devices. */ diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 440e99632c86..edb41639514b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2825,8 +2825,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS; } int combinedGenericMotionEventSources = 0; + int combinedMotionEventObservedSources = 0; for (AccessibilityServiceConnection connection : userState.mBoundServices) { combinedGenericMotionEventSources |= connection.mGenericMotionEventSources; + combinedMotionEventObservedSources |= connection.mObservedMotionEventSources; } if (combinedGenericMotionEventSources != 0) { flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS; @@ -2845,6 +2847,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags); mInputFilter.setCombinedGenericMotionEventSources( combinedGenericMotionEventSources); + mInputFilter.setCombinedMotionEventObservedSources( + combinedMotionEventObservedSources); } else { if (mHasInputFilter) { mHasInputFilter = false; diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index 34787a390d48..145303df7b0b 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -554,6 +554,10 @@ final class ContentCapturePerUserService if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service"); return; } + if (mRemoteService.getServiceInterface() == null) { + if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): remote service is dead or unbound"); + return; + } final ActivityEvent event = new ActivityEvent(activityId, componentName, type); if (mMaster.verbose) Slog.v(mTag, "onActivityEvent(): " + event); diff --git a/services/core/Android.bp b/services/core/Android.bp index 20a3b9ada85d..a0ccbf3acf0a 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -154,6 +154,7 @@ java_library_static { static_libs: [ "android.frameworks.location.altitude-V1-java", // AIDL + "android.frameworks.vibrator-V1-java", // AIDL "android.hardware.authsecret-V1.0-java", "android.hardware.authsecret-V1-java", "android.hardware.boot-V1.0-java", // HIDL @@ -165,7 +166,7 @@ java_library_static { "android.hardware.health-V1.0-java", // HIDL "android.hardware.health-V2.0-java", // HIDL "android.hardware.health-V2.1-java", // HIDL - "android.hardware.health-V2-java", // AIDL + "android.hardware.health-V3-java", // AIDL "android.hardware.health-translate-java", "android.hardware.light-V1-java", "android.hardware.security.rkp-V3-java", diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 3280afdf6703..627a62ee0496 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -42,6 +42,7 @@ import android.debug.AdbManager; import android.debug.AdbNotifications; import android.debug.AdbProtoEnums; import android.debug.AdbTransportType; +import android.debug.IAdbTransport; import android.debug.PairDevice; import android.net.ConnectivityManager; import android.net.LocalSocket; @@ -66,6 +67,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.service.adb.AdbDebuggingManagerProto; +import android.text.TextUtils; import android.util.AtomicFile; import android.util.Base64; import android.util.Slog; @@ -679,16 +681,17 @@ public class AdbDebuggingManager { return; } - // Check for network change - String bssid = wifiInfo.getBSSID(); - if (bssid == null || bssid.isEmpty()) { - Slog.e(TAG, "Unable to get the wifi ap's BSSID. Disabling adbwifi."); - Settings.Global.putInt(mContentResolver, - Settings.Global.ADB_WIFI_ENABLED, 0); - return; - } synchronized (mAdbConnectionInfo) { - if (!bssid.equals(mAdbConnectionInfo.getBSSID())) { + // Check for network change + final String bssid = wifiInfo.getBSSID(); + if (TextUtils.isEmpty(bssid)) { + Slog.e(TAG, + "Unable to get the wifi ap's BSSID. Disabling adbwifi."); + Settings.Global.putInt(mContentResolver, + Settings.Global.ADB_WIFI_ENABLED, 0); + return; + } + if (!TextUtils.equals(bssid, mAdbConnectionInfo.getBSSID())) { Slog.i(TAG, "Detected wifi network change. Disabling adbwifi."); Settings.Global.putInt(mContentResolver, Settings.Global.ADB_WIFI_ENABLED, 0); @@ -1397,7 +1400,7 @@ public class AdbDebuggingManager { } String bssid = wifiInfo.getBSSID(); - if (bssid == null || bssid.isEmpty()) { + if (TextUtils.isEmpty(bssid)) { Slog.e(TAG, "Unable to get the wifi ap's BSSID."); return null; } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index f8f3d82556fa..ace2cfd8a307 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -33,6 +33,7 @@ import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.RequiresNoPermission; import android.annotation.SuppressLint; +import android.app.AlarmManager; import android.app.StatsManager; import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothActivityEnergyInfo; @@ -427,13 +428,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig); mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig, mStats.getHistory()); - final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger( - com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration); - final long powerStatsAggregationPeriod = context.getResources().getInteger( - com.android.internal.R.integer.config_powerStatsAggregationPeriod); - mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, - aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore, - Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats); + mPowerStatsScheduler = createPowerStatsScheduler(mContext); PowerStatsExporter powerStatsExporter = new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator); mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, @@ -445,6 +440,23 @@ public final class BatteryStatsService extends IBatteryStats.Stub mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config")); } + private PowerStatsScheduler createPowerStatsScheduler(Context context) { + final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration); + final long powerStatsAggregationPeriod = context.getResources().getInteger( + com.android.internal.R.integer.config_powerStatsAggregationPeriod); + PowerStatsScheduler.AlarmScheduler alarmScheduler = + (triggerAtMillis, tag, onAlarmListener, aHandler) -> { + AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); + alarmManager.set(AlarmManager.ELAPSED_REALTIME, triggerAtMillis, tag, + onAlarmListener, aHandler); + }; + return new PowerStatsScheduler(mStats::schedulePowerStatsSampleCollection, + mPowerStatsAggregator, aggregatedPowerStatsSpanDuration, + powerStatsAggregationPeriod, mPowerStatsStore, alarmScheduler, Clock.SYSTEM_CLOCK, + mMonotonicClock, () -> mStats.getHistory().getStartTime(), mHandler); + } + private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() { AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index b2082d9e8dc0..b4cd6a31291e 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -401,7 +401,7 @@ class ProcessRecord implements WindowProcessListener { /** * All about the process state info (proc state, oom adj score) in this process. */ - final ProcessStateRecord mState; + ProcessStateRecord mState; /** * All about the state info of the optimizer when the process is cached. diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 173c452fd8cb..850449595d74 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -330,8 +330,11 @@ import java.util.Objects; TextUtils.isEmpty(address) ? null : mBluetoothRouteController.getRouteIdForBluetoothAddress(address); - return createMediaRoute2Info( - routeId, audioDeviceInfo.getType(), audioDeviceInfo.getProductName(), address); + // We use the name from the port instead AudioDeviceInfo#getProductName because the latter + // replaces empty names with the name of the device (example: Pixel 8). In that case we want + // to derive a name ourselves from the type instead. + String deviceName = audioDeviceInfo.getPort().name(); + return createMediaRoute2Info(routeId, audioDeviceInfo.getType(), deviceName, address); } /** @@ -339,8 +342,8 @@ import java.util.Objects; * * @param routeId A route id, or null to use an id pre-defined for the given {@code type}. * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}. - * @param productName The product name as obtained from {@link - * AudioDeviceInfo#getProductName()}, or null to use a predefined name for the given {@code + * @param deviceName A human readable name to populate the route's {@link + * MediaRoute2Info#getName name}, or null to use a predefined name for the given {@code * type}. * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link * BluetoothDevice#getAddress()}. @@ -350,7 +353,7 @@ import java.util.Objects; private MediaRoute2Info createMediaRoute2Info( @Nullable String routeId, int audioDeviceInfoType, - @Nullable CharSequence productName, + @Nullable CharSequence deviceName, @Nullable String address) { SystemRouteInfo systemRouteInfo = AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType); @@ -359,7 +362,7 @@ import java.util.Objects; // earpiece. return null; } - CharSequence humanReadableName = productName; + CharSequence humanReadableName = deviceName; if (TextUtils.isEmpty(humanReadableName)) { humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource); } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index dcac8c98d19f..da017453ed8b 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -20,3 +20,17 @@ flag { description: "This flag controls the refactoring of NMS to NotificationAttentionHelper" bug: "291907312" } + +flag { + name: "cross_app_polite_notifications" + namespace: "systemui" + description: "This flag controls the cross-app effect of polite notifications" + bug: "270456865" +} + +flag { + name: "vibrate_while_unlocked" + namespace: "systemui" + description: "This flag controls the vibrate while unlocked setting of polite notifications" + bug: "270456865" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/DeletePackageAction.java b/services/core/java/com/android/server/pm/DeletePackageAction.java index 8ef6601f7684..31544d5308fb 100644 --- a/services/core/java/com/android/server/pm/DeletePackageAction.java +++ b/services/core/java/com/android/server/pm/DeletePackageAction.java @@ -16,17 +16,19 @@ package com.android.server.pm; +import android.annotation.NonNull; import android.os.UserHandle; final class DeletePackageAction { public final PackageSetting mDeletingPs; public final PackageSetting mDisabledPs; + @NonNull public final PackageRemovedInfo mRemovedInfo; public final int mFlags; public final UserHandle mUser; DeletePackageAction(PackageSetting deletingPs, PackageSetting disabledPs, - PackageRemovedInfo removedInfo, int flags, UserHandle user) { + @NonNull PackageRemovedInfo removedInfo, int flags, UserHandle user) { mDeletingPs = deletingPs; mDisabledPs = disabledPs; mRemovedInfo = removedInfo; diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 93836266d1f4..dcf921c90885 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -370,7 +370,7 @@ final class DeletePackageHelper { @GuardedBy("mPm.mInstallLock") public boolean deletePackageLIF(@NonNull String packageName, UserHandle user, boolean deleteCodeAndResources, @NonNull int[] allUserHandles, int flags, - PackageRemovedInfo outInfo, boolean writeSettings) { + @NonNull PackageRemovedInfo outInfo, boolean writeSettings) { final DeletePackageAction action; synchronized (mPm.mLock) { final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); @@ -410,8 +410,8 @@ final class DeletePackageHelper { * deleted, {@code null} otherwise. */ @Nullable - public static DeletePackageAction mayDeletePackageLocked( - PackageRemovedInfo outInfo, PackageSetting ps, @Nullable PackageSetting disabledPs, + public static DeletePackageAction mayDeletePackageLocked(@NonNull PackageRemovedInfo outInfo, + PackageSetting ps, @Nullable PackageSetting disabledPs, int flags, UserHandle user) { if (ps == null) { return null; @@ -460,12 +460,18 @@ final class DeletePackageHelper { } final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier(); - if (outInfo != null) { - // Remember which users are affected, before the installed states are modified - outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL) - ? ps.queryUsersInstalledOrHasData(allUserHandles) - : new int[]{userId}; - } + // Remember which users are affected, before the installed states are modified + outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL) + ? ps.queryUsersInstalledOrHasData(allUserHandles) + : new int[]{userId}; + outInfo.populateBroadcastUsers(ps); + outInfo.mDataRemoved = (flags & PackageManager.DELETE_KEEP_DATA) == 0; + outInfo.mRemovedPackage = ps.getPackageName(); + outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName; + outInfo.mIsStaticSharedLib = + ps.getPkg() != null && ps.getPkg().getStaticSharedLibraryName() != null; + outInfo.mIsExternal = ps.isExternalStorage(); + outInfo.mRemovedPackageVersionCode = ps.getVersionCode(); if ((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0) && userId != UserHandle.USER_ALL) { @@ -503,7 +509,8 @@ final class DeletePackageHelper { } } if (clearPackageStateAndReturn) { - mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, outInfo, flags); + mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, flags); + outInfo.mRemovedAppId = ps.getAppId(); mPm.scheduleWritePackageRestrictions(user); return; } @@ -529,12 +536,8 @@ final class DeletePackageHelper { // If the package removed had SUSPEND_APPS, unset any restrictions that might have been in // place for all affected users. - int[] affectedUserIds = (outInfo != null) ? outInfo.mRemovedUsers : null; - if (affectedUserIds == null) { - affectedUserIds = mPm.resolveUserIds(userId); - } final Computer snapshot = mPm.snapshotComputer(); - for (final int affectedUserId : affectedUserIds) { + for (final int affectedUserId : outInfo.mRemovedUsers) { if (hadSuspendAppsPermission.get(affectedUserId)) { mPm.unsuspendForSuspendingPackage(snapshot, packageName, affectedUserId); mPm.removeAllDistractingPackageRestrictions(snapshot, affectedUserId); @@ -542,24 +545,20 @@ final class DeletePackageHelper { } // Take a note whether we deleted the package for all users - if (outInfo != null) { - synchronized (mPm.mLock) { - outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null; - } + synchronized (mPm.mLock) { + outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null; } } @GuardedBy("mPm.mInstallLock") private void deleteInstalledPackageLIF(PackageSetting ps, boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles, - PackageRemovedInfo outInfo, boolean writeSettings) { + @NonNull PackageRemovedInfo outInfo, boolean writeSettings) { synchronized (mPm.mLock) { - if (outInfo != null) { - outInfo.mUid = ps.getAppId(); - outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList( - mPm.snapshotComputer(), ps, allUserHandles, - mPm.mSettings.getPackagesLocked()); - } + outInfo.mUid = ps.getAppId(); + outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList( + mPm.snapshotComputer(), ps, allUserHandles, + mPm.mSettings.getPackagesLocked()); } // Delete package data from internal structures and also remove data if flag is set @@ -567,7 +566,7 @@ final class DeletePackageHelper { ps, allUserHandles, outInfo, flags, writeSettings); // Delete application code and resources only for parent packages - if (deleteCodeAndResources && (outInfo != null)) { + if (deleteCodeAndResources) { outInfo.mArgs = new InstallArgs( ps.getPathString(), getAppDexInstructionSets( ps.getPrimaryCpuAbiLegacy(), ps.getSecondaryCpuAbiLegacy())); @@ -639,7 +638,7 @@ final class DeletePackageHelper { int flags = action.mFlags; final PackageSetting deletedPs = action.mDeletingPs; final PackageRemovedInfo outInfo = action.mRemovedInfo; - final boolean applyUserRestrictions = outInfo != null && (outInfo.mOrigUsers != null); + final boolean applyUserRestrictions = outInfo.mOrigUsers != null; final AndroidPackage deletedPkg = deletedPs.getPkg(); // Confirm if the system package has been updated // An updated system app can be deleted. This will also have to restore @@ -662,10 +661,8 @@ final class DeletePackageHelper { } } - if (outInfo != null) { - // Delete the updated package - outInfo.mIsRemovedPackageSystemUpdate = true; - } + // Delete the updated package + outInfo.mIsRemovedPackageSystemUpdate = true; if (disabledPs.getVersionCode() < deletedPs.getVersionCode() || disabledPs.getAppId() != deletedPs.getAppId()) { diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 83a6f10f0e2a..f1c062763183 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -4126,7 +4126,7 @@ final class InstallPackageHelper { null /* request */)) { mDeletePackageHelper.deletePackageLIF( parsedPackage.getPackageName(), null, true, - mPm.mUserManager.getUserIds(), 0, null, false); + mPm.mUserManager.getUserIds(), 0, new PackageRemovedInfo(), false); } } else if (newPkgVersionGreater || newSharedUserSetting) { // The application on /system is newer than the application on /data. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2880f84c6445..c5b006c4c77d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3044,6 +3044,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + @NonNull int[] resolveUserIds(int userId) { return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId }; } diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 52b31319cc19..109d7ba1d29e 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -252,8 +252,7 @@ final class RemovePackageHelper { } } - public void clearPackageStateForUserLIF(PackageSetting ps, int userId, - PackageRemovedInfo outInfo, int flags) { + public void clearPackageStateForUserLIF(PackageSetting ps, int userId, int flags) { final AndroidPackage pkg; final SharedUserSetting sus; synchronized (mPm.mLock) { @@ -287,25 +286,12 @@ final class RemovePackageHelper { } mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg, sharedUserPkgs, userId); - - if (outInfo != null) { - if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) { - outInfo.mDataRemoved = true; - } - outInfo.mRemovedPackage = ps.getPackageName(); - outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName; - outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null; - outInfo.mRemovedAppId = ps.getAppId(); - outInfo.mBroadcastUsers = outInfo.mRemovedUsers; - outInfo.mIsExternal = ps.isExternalStorage(); - outInfo.mRemovedPackageVersionCode = ps.getVersionCode(); - } } // Called to clean up disabled system packages public void removePackageData(final PackageSetting deletedPs, @NonNull int[] allUserHandles) { synchronized (mPm.mInstallLock) { - removePackageDataLIF(deletedPs, allUserHandles, /* outInfo= */ null, + removePackageDataLIF(deletedPs, allUserHandles, new PackageRemovedInfo(), /* flags= */ 0, /* writeSettings= */ false); } } @@ -318,20 +304,11 @@ final class RemovePackageHelper { */ @GuardedBy("mPm.mInstallLock") public void removePackageDataLIF(final PackageSetting deletedPs, @NonNull int[] allUserHandles, - PackageRemovedInfo outInfo, int flags, boolean writeSettings) { + @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) { String packageName = deletedPs.getPackageName(); if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs); // Retrieve object to delete permissions for shared user later on final AndroidPackage deletedPkg = deletedPs.getPkg(); - if (outInfo != null) { - outInfo.mRemovedPackage = packageName; - outInfo.mInstallerPackageName = deletedPs.getInstallSource().mInstallerPackageName; - outInfo.mIsStaticSharedLib = deletedPkg != null - && deletedPkg.getStaticSharedLibraryName() != null; - outInfo.populateBroadcastUsers(deletedPs); - outInfo.mIsExternal = deletedPs.isExternalStorage(); - outInfo.mRemovedPackageVersionCode = deletedPs.getVersionCode(); - } removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0); if (!deletedPs.isSystem()) { @@ -355,9 +332,6 @@ final class RemovePackageHelper { mAppDataHelper.destroyAppDataLIF(resolvedPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); mAppDataHelper.destroyAppProfilesLIF(resolvedPkg.getPackageName()); - if (outInfo != null) { - outInfo.mDataRemoved = true; - } } int removedAppId = -1; @@ -373,9 +347,8 @@ final class RemovePackageHelper { mPm.mAppsFilter.removePackage(snapshot, snapshot.getPackageStateInternal(packageName)); removedAppId = mPm.mSettings.removePackageLPw(packageName); - if (outInfo != null) { - outInfo.mRemovedAppId = removedAppId; - } + outInfo.mRemovedAppId = removedAppId; + if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) { // If we don't have a disabled system package to reinstall, the package is // really gone and its permission state should be removed. @@ -403,8 +376,8 @@ final class RemovePackageHelper { mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL); }); } - } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate - && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) { + } else if (!deletedPs.isSystem() && !outInfo.mIsUpdate + && outInfo.mRemovedUsers != null && !deletedPs.isExternalStorage()) { // For non-system uninstalls with DELETE_KEEP_DATA, set the installed state to false // for affected users. This does not apply to app updates where the old apk is replaced // but the old data remains. @@ -424,7 +397,7 @@ final class RemovePackageHelper { // make sure to preserve per-user installed state if this removal was just // a downgrade of a system app to the factory package boolean installedStateChanged = false; - if (outInfo != null && outInfo.mOrigUsers != null && deletedPs.isSystem()) { + if (outInfo.mOrigUsers != null && deletedPs.isSystem()) { if (DEBUG_REMOVE) { Slog.d(TAG, "Propagating install state across downgrade"); } diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index 94495bf462f2..ec8af2ecd070 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -731,7 +731,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable ? PackageManager.DELETE_KEEP_DATA : 0; synchronized (mPm.mInstallLock) { mDeletePackageHelper.deletePackageLIF(pkg.getPackageName(), null, true, - mPm.mUserManager.getUserIds(), flags, null, + mPm.mUserManager.getUserIds(), flags, new PackageRemovedInfo(), true); } } diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 70aa19ae9cef..b607502baada 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -256,13 +256,12 @@ public final class StorageEventHelper extends StorageEventListener { final AndroidPackage pkg = ps.getPkg(); final int deleteFlags = PackageManager.DELETE_KEEP_DATA; - final PackageRemovedInfo outInfo = new PackageRemovedInfo(); try (PackageFreezer freezer = mPm.freezePackageForDelete(ps.getPackageName(), UserHandle.USER_ALL, deleteFlags, "unloadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER)) { if (mDeletePackageHelper.deletePackageLIF(ps.getPackageName(), null, false, - userIds, deleteFlags, outInfo, false)) { + userIds, deleteFlags, new PackageRemovedInfo(), false)) { unloaded.add(pkg); } else { Slog.w(TAG, "Failed to unload " + ps.getPath()); diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java index abfe9debc7de..e1eb8f07dd2c 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -20,7 +20,6 @@ import android.annotation.Nullable; import android.os.ConditionVariable; import android.os.Handler; import android.os.PersistableBundle; -import android.util.FastImmutableArraySet; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -30,8 +29,9 @@ import com.android.internal.os.PowerStats; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.function.Consumer; -import java.util.stream.Stream; /** * Collects snapshots of power-related system statistics. @@ -246,8 +246,7 @@ public abstract class PowerStatsCollector { @GuardedBy("this") @SuppressWarnings("unchecked") - private volatile FastImmutableArraySet<Consumer<PowerStats>> mConsumerList = - new FastImmutableArraySet<Consumer<PowerStats>>(new Consumer[0]); + private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList(); public PowerStatsCollector(Handler handler, long throttlePeriodMs, Clock clock) { mHandler = handler; @@ -262,9 +261,13 @@ public abstract class PowerStatsCollector { @SuppressWarnings("unchecked") public void addConsumer(Consumer<PowerStats> consumer) { synchronized (this) { - mConsumerList = new FastImmutableArraySet<Consumer<PowerStats>>( - Stream.concat(mConsumerList.stream(), Stream.of(consumer)) - .toArray(Consumer[]::new)); + if (mConsumerList.contains(consumer)) { + return; + } + + List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList); + newList.add(consumer); + mConsumerList = Collections.unmodifiableList(newList); } } @@ -275,9 +278,9 @@ public abstract class PowerStatsCollector { @SuppressWarnings("unchecked") public void removeConsumer(Consumer<PowerStats> consumer) { synchronized (this) { - mConsumerList = new FastImmutableArraySet<Consumer<PowerStats>>( - mConsumerList.stream().filter(c -> c != consumer) - .toArray(Consumer[]::new)); + List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList); + newList.remove(consumer); + mConsumerList = Collections.unmodifiableList(newList); } } @@ -302,8 +305,9 @@ public abstract class PowerStatsCollector { if (stats == null) { return; } - for (Consumer<PowerStats> consumer : mConsumerList) { - consumer.accept(stats); + List<Consumer<PowerStats>> consumerList = mConsumerList; + for (int i = consumerList.size() - 1; i >= 0; i--) { + consumerList.get(i).accept(stats); } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java index 97d872a1a539..121a98bab37a 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java @@ -18,7 +18,6 @@ package com.android.server.power.stats; import android.annotation.DurationMillisLong; import android.app.AlarmManager; -import android.content.Context; import android.os.ConditionVariable; import android.os.Handler; import android.util.IndentingPrintWriter; @@ -30,6 +29,7 @@ import com.android.internal.os.MonotonicClock; import java.io.PrintWriter; import java.util.Calendar; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; /** * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in @@ -39,7 +39,7 @@ public class PowerStatsScheduler { private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1); private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1); - private final Context mContext; + private final AlarmScheduler mAlarmScheduler; private boolean mEnablePeriodicPowerStatsCollection; @DurationMillisLong private final long mAggregatedPowerStatsSpanDuration; @@ -49,24 +49,38 @@ public class PowerStatsScheduler { private final Clock mClock; private final MonotonicClock mMonotonicClock; private final Handler mHandler; - private final BatteryStatsImpl mBatteryStats; + private final Runnable mPowerStatsCollector; + private final Supplier<Long> mEarliestAvailableBatteryHistoryTimeMs; private final PowerStatsAggregator mPowerStatsAggregator; private long mLastSavedSpanEndMonotonicTime; - public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator, + /** + * External dependency on AlarmManager. + */ + public interface AlarmScheduler { + /** + * Should use AlarmManager to schedule an inexact, non-wakeup alarm. + */ + void scheduleAlarm(long triggerAtMillis, String tag, + AlarmManager.OnAlarmListener onAlarmListener, Handler handler); + } + + public PowerStatsScheduler(Runnable powerStatsCollector, + PowerStatsAggregator powerStatsAggregator, @DurationMillisLong long aggregatedPowerStatsSpanDuration, @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore, - Clock clock, MonotonicClock monotonicClock, Handler handler, - BatteryStatsImpl batteryStats) { - mContext = context; + AlarmScheduler alarmScheduler, Clock clock, MonotonicClock monotonicClock, + Supplier<Long> earliestAvailableBatteryHistoryTimeMs, Handler handler) { mPowerStatsAggregator = powerStatsAggregator; mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration; mPowerStatsAggregationPeriod = powerStatsAggregationPeriod; mPowerStatsStore = powerStatsStore; + mAlarmScheduler = alarmScheduler; mClock = clock; mMonotonicClock = monotonicClock; mHandler = handler; - mBatteryStats = batteryStats; + mPowerStatsCollector = powerStatsCollector; + mEarliestAvailableBatteryHistoryTimeMs = earliestAvailableBatteryHistoryTimeMs; } /** @@ -81,9 +95,8 @@ public class PowerStatsScheduler { } private void scheduleNextPowerStatsAggregation() { - AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); - alarmManager.set(AlarmManager.ELAPSED_REALTIME, - mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats", + mAlarmScheduler.scheduleAlarm(mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, + "PowerStats", () -> { schedulePowerStatsAggregation(); mHandler.post(this::scheduleNextPowerStatsAggregation); @@ -96,7 +109,7 @@ public class PowerStatsScheduler { @VisibleForTesting public void schedulePowerStatsAggregation() { // Catch up the power stats collectors - mBatteryStats.schedulePowerStatsSampleCollection(); + mPowerStatsCollector.run(); mHandler.post(this::aggregateAndStorePowerStats); } @@ -105,7 +118,7 @@ public class PowerStatsScheduler { long currentMonotonicTime = mMonotonicClock.monotonicTime(); long startTime = getLastSavedSpanEndMonotonicTime(); if (startTime < 0) { - startTime = mBatteryStats.getHistory().getStartTime(); + startTime = mEarliestAvailableBatteryHistoryTimeMs.get(); } long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration, mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis); diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java new file mode 100644 index 000000000000..2eeb903bb551 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java @@ -0,0 +1,105 @@ +/* + * 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.vibrator; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.frameworks.vibrator.IVibratorControlService; +import android.frameworks.vibrator.IVibratorController; +import android.frameworks.vibrator.VibrationParam; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.Objects; + +/** + * Implementation of {@link IVibratorControlService} which allows the registration of + * {@link IVibratorController} to set and receive vibration params. + * + * @hide + */ +public final class VibratorControlService extends IVibratorControlService.Stub { + private static final String TAG = "VibratorControlService"; + + private final VibratorControllerHolder mVibratorControllerHolder; + private final Object mLock; + + public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) { + mVibratorControllerHolder = vibratorControllerHolder; + mLock = lock; + } + + @Override + public void registerVibratorController(IVibratorController controller) + throws RemoteException { + synchronized (mLock) { + mVibratorControllerHolder.setVibratorController(controller); + } + } + + @Override + public void unregisterVibratorController(@NonNull IVibratorController controller) + throws RemoteException { + Objects.requireNonNull(controller); + + synchronized (mLock) { + if (mVibratorControllerHolder.getVibratorController() == null) { + Slog.w(TAG, "Received request to unregister IVibratorController = " + + controller + ", but no controller was previously registered. Request " + + "Ignored."); + return; + } + if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(), + controller.asBinder())) { + Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided " + + "controller doesn't match the registered one. " + this); + return; + } + mVibratorControllerHolder.setVibratorController(null); + } + } + + @Override + public void setVibrationParams( + @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token) + throws RemoteException { + // TODO(b/305939964): Add set vibration implementation. + } + + @Override + public void clearVibrationParams(int types, IVibratorController token) throws RemoteException { + // TODO(b/305939964): Add clear vibration implementation. + } + + @Override + public void onRequestVibrationParamsComplete( + IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) + throws RemoteException { + // TODO(305942827): Cache the vibration params in VibrationScaler + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return this.VERSION; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return this.HASH; + } +} diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java new file mode 100644 index 000000000000..63e69db9480f --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java @@ -0,0 +1,70 @@ +/* + * 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.vibrator; + +import android.annotation.NonNull; +import android.frameworks.vibrator.IVibratorController; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +/** + * Holder class for {@link IVibratorController}. + * + * @hide + */ +public final class VibratorControllerHolder implements IBinder.DeathRecipient { + private static final String TAG = "VibratorControllerHolder"; + + private IVibratorController mVibratorController; + + public IVibratorController getVibratorController() { + return mVibratorController; + } + + /** + * Sets the {@link IVibratorController} in {@link VibratorControllerHolder} to the new + * controller. This will also take care of registering and unregistering death notifications + * for the cached {@link IVibratorController}. + */ + public void setVibratorController(IVibratorController controller) { + try { + if (mVibratorController != null) { + mVibratorController.asBinder().unlinkToDeath(this, 0); + } + mVibratorController = controller; + if (mVibratorController != null) { + mVibratorController.asBinder().linkToDeath(this, 0); + } + } catch (RemoteException e) { + Slog.wtf(TAG, "Failed to set IVibratorController: " + this, e); + } + } + + @Override + public void binderDied(@NonNull IBinder deadBinder) { + if (deadBinder == mVibratorController.asBinder()) { + setVibratorController(null); + } + } + + @Override + public void binderDied() { + // Should not be used as binderDied(IBinder who) is overridden. + Slog.wtf(TAG, "binderDied() called unexpectedly."); + } +} diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 7d4bd3baf613..fc824abd80f5 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -91,6 +91,8 @@ import java.util.function.Function; public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String TAG = "VibratorManagerService"; private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; + private static final String VIBRATOR_CONTROL_SERVICE = + "android.frameworks.vibrator.IVibratorControlService/default"; private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); @@ -269,6 +271,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED); injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); + if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) { + injector.addService(VIBRATOR_CONTROL_SERVICE, + new VibratorControlService(new VibratorControllerHolder(), mLock)); + } + } /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */ diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 03d6c2cab828..ae10ce3690aa 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5149,8 +5149,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** @return the orientation of the display when it's rotation is ROTATION_0. */ int getNaturalOrientation() { - return mBaseDisplayWidth < mBaseDisplayHeight - ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; + final Configuration config = getConfiguration(); + if (config.windowConfiguration.getDisplayRotation() == ROTATION_0) { + return config.orientation; + } + final Rect frame = mDisplayPolicy.getDecorInsetsInfo( + ROTATION_0, mBaseDisplayWidth, mBaseDisplayHeight).mConfigFrame; + return frame.width() <= frame.height() ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; } void performLayout(boolean initial, boolean updateInputWindows) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a872fd0baaae..4b99432b2943 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -63,6 +63,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; +import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS; @@ -1178,6 +1179,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.mRootWindowContainer.moveActivityToPinnedRootTask( pipActivity, null /* launchIntoPipHostActivity */, "moveActivityToPinnedRootTask", null /* transition */, entryBounds); + + // Continue the pausing process after potential task reparenting. + if (pipActivity.isState(PAUSING) && pipActivity.mPauseSchedulePendingForPip) { + pipActivity.getTask().schedulePauseActivity( + pipActivity, false /* userLeaving */, + false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip"); + } + effects |= TRANSACT_EFFECTS_LIFECYCLE; break; } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index fd5f7a3f5cfa..5721750fbf63 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -66,6 +66,7 @@ import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.os.Binder; import android.os.Build; +import android.os.DeadObjectException; import android.os.FactoryTest; import android.os.LocaleList; import android.os.Message; @@ -1675,6 +1676,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio @NonNull ClientTransactionItem transactionItem) { try { mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem); + } catch (DeadObjectException e) { + // Expected if the process has been killed. + Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem=" + + transactionItem + " owner=" + mOwner); } catch (Exception e) { Slog.e(TAG_CONFIGURATION, "Failed to schedule ClientTransactionItem=" + transactionItem + " owner=" + mOwner, e); diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig index 47c2a1b079f8..29e258cc90ff 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -5,4 +5,12 @@ flag { namespace: "display_manager" description: "Feature flag for dual display blocking" bug: "278667199" +} + +flag { + name: "enable_foldables_posture_based_closed_state" + namespace: "windowing_frontend" + description: "Enables smarter closed device state state for foldable devices" + bug: "309792734" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/services/manifest_services.xml b/services/manifest_services.xml index 76389154a885..eae159fe9e89 100644 --- a/services/manifest_services.xml +++ b/services/manifest_services.xml @@ -4,4 +4,9 @@ <version>1</version> <fqname>IAltitudeService/default</fqname> </hal> + <hal format="aidl"> + <name>android.frameworks.vibrator</name> + <version>1</version> + <fqname>IVibratorControlService/default</fqname> + </hal> </manifest> diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 820e44fd2ff7..0fd424b89837 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -334,6 +334,7 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { final boolean dead = (behavior == ProcessBehavior.DEAD); final ProcessRecord r = spy(new ProcessRecord(mAms, ai, processName, ai.uid)); + r.mState = spy(r.mState); r.setPid(mNextPid.getAndIncrement()); mActiveProcesses.add(r); @@ -788,8 +789,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { }) { // Confirm expected OOM adjustments; we were invoked once to upgrade // and once to downgrade - assertEquals(String.valueOf(receiverApp), ActivityManager.PROCESS_STATE_RECEIVER, - receiverApp.mState.getReportedProcState()); + verify(receiverApp.mState, times(1).description(String.valueOf(receiverApp))) + .setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER); verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp)); if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) { diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index 07197b1ab9df..05e0e8f4a4f7 100644 --- a/services/tests/powerstatstests/Android.bp +++ b/services/tests/powerstatstests/Android.bp @@ -3,6 +3,20 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +filegroup { + name: "power_stats_ravenwood_tests", + srcs: [ + "src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java", + "src/com/android/server/power/stats/AggregatedPowerStatsTest.java", + "src/com/android/server/power/stats/MultiStateStatsTest.java", + "src/com/android/server/power/stats/PowerStatsAggregatorTest.java", + "src/com/android/server/power/stats/PowerStatsCollectorTest.java", + "src/com/android/server/power/stats/PowerStatsSchedulerTest.java", + "src/com/android/server/power/stats/PowerStatsStoreTest.java", + "src/com/android/server/power/stats/PowerStatsUidResolverTest.java", + ], +} + android_test { name: "PowerStatsTests", @@ -12,8 +26,7 @@ android_test { ], exclude_srcs: [ - "src/com/android/server/power/stats/MultiStateStatsTest.java", - "src/com/android/server/power/stats/PowerStatsStoreTest.java", + ":power_stats_ravenwood_tests", ], static_libs: [ @@ -65,10 +78,12 @@ android_ravenwood_test { "modules-utils-binary-xml", "androidx.annotation_annotation", "androidx.test.rules", + "truth", + "mockito_ravenwood", ], srcs: [ - "src/com/android/server/power/stats/MultiStateStatsTest.java", - "src/com/android/server/power/stats/PowerStatsStoreTest.java", + ":power_stats_ravenwood_tests", + "src/com/android/server/power/stats/MockClock.java", ], auto_gen_config: true, } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java index 6d61dc8d31fa..af83be04db7d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java @@ -35,7 +35,7 @@ import java.util.Objects; @RunWith(AndroidJUnit4.class) @SmallTest -public class AggregatePowerStatsProcessorTest { +public class AggregatedPowerStatsProcessorTest { @Test public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java index e8f46b30fb8c..1b045c532759 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java @@ -22,12 +22,10 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import android.os.BatteryConsumer; -import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,11 +36,6 @@ import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest public class MultiStateStatsTest { - - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() - .build(); - public static final int DIMENSION_COUNT = 2; @Test diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java index 67049871f396..2456636970fa 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.mock; import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.PersistableBundle; -import android.text.format.DateFormat; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; @@ -39,7 +38,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.text.ParseException; -import java.util.Calendar; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import java.util.TimeZone; @@ -60,7 +60,7 @@ public class PowerStatsAggregatorTest { public void setup() throws ParseException { mHistory = new BatteryStatsHistory(32, 1024, mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock, - mMonotonicClock); + mMonotonicClock, mock(BatteryStatsHistory.TraceDelegate.class)); AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); config.trackPowerComponent(TEST_POWER_COMPONENT) @@ -179,9 +179,9 @@ public class PowerStatsAggregatorTest { @NonNull private static CharSequence formatDateTime(long timeInMillis) { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - cal.setTimeInMillis(timeInMillis); - return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT")); + return format.format(new Date(timeInMillis)); } @Test diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java index 330f698277f8..17a7d3ecf9d3 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java @@ -22,6 +22,7 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.PersistableBundle; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -29,12 +30,18 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.PowerStats; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class PowerStatsCollectorTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + private final MockClock mMockClock = new MockClock(); private final HandlerThread mHandlerThread = new HandlerThread("test"); private Handler mHandler; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java index 7257a94cbb9a..beec66156fe4 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java @@ -24,26 +24,26 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import android.content.Context; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; +import android.platform.test.ravenwood.RavenwoodRule; -import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.MonotonicClock; -import com.android.internal.os.PowerProfile; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -51,39 +51,46 @@ import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) public class PowerStatsSchedulerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + private PowerStatsStore mPowerStatsStore; private Handler mHandler; private MockClock mClock = new MockClock(); private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); - private MockBatteryStatsImpl mBatteryStats; private PowerStatsScheduler mPowerStatsScheduler; - private PowerProfile mPowerProfile; private PowerStatsAggregator mPowerStatsAggregator; private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; + private List<Long> mScheduledAlarms = new ArrayList<>(); + private boolean mPowerStatsCollectionOccurred; + + private static final int START_REALTIME = 7654321; @Before @SuppressWarnings("GuardedBy") - public void setup() { - final Context context = InstrumentationRegistry.getContext(); - + public void setup() throws IOException { TimeZone.setDefault(TimeZone.getTimeZone("UTC")); mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli(); - mClock.realtime = 7654321; + mClock.realtime = START_REALTIME; HandlerThread bgThread = new HandlerThread("bg thread"); bgThread.start(); - File systemDir = context.getCacheDir(); mHandler = new Handler(bgThread.getLooper()); mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig(); - mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig); - mPowerProfile = mock(PowerProfile.class); - when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0); - mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile); + mPowerStatsStore = new PowerStatsStore( + Files.createTempDirectory("PowerStatsSchedulerTest").toFile(), + mHandler, mAggregatedPowerStatsConfig); mPowerStatsAggregator = mock(PowerStatsAggregator.class); - mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, - TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock, - mMonotonicClock, mHandler, mBatteryStats); + mPowerStatsScheduler = new PowerStatsScheduler( + () -> mPowerStatsCollectionOccurred = true, + mPowerStatsAggregator, TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), + mPowerStatsStore, + ((triggerAtMillis, tag, onAlarmListener, handler) -> + mScheduledAlarms.add(triggerAtMillis)), + mClock, mMonotonicClock, () -> 12345L, mHandler); } @Test @@ -113,7 +120,7 @@ public class PowerStatsSchedulerTest { long endTimeWallClock = mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime); - assertThat(startTime).isEqualTo(7654321 + 123); + assertThat(startTime).isEqualTo(START_REALTIME + 123); assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30)); assertThat(Instant.ofEpochMilli(endTimeWallClock)) .isEqualTo(Instant.parse("2023-01-02T04:00:00Z")); @@ -142,11 +149,15 @@ public class PowerStatsSchedulerTest { }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class)); - mPowerStatsScheduler.schedulePowerStatsAggregation(); + mPowerStatsScheduler.start(/*enabled*/ true); ConditionVariable done = new ConditionVariable(); mHandler.post(done::open); done.block(); + assertThat(mPowerStatsCollectionOccurred).isTrue(); + assertThat(mScheduledAlarms).containsExactly( + START_REALTIME + TimeUnit.MINUTES.toMillis(90) + TimeUnit.HOURS.toMillis(1)); + verify(mPowerStatsAggregator, times(2)) .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class)); @@ -155,7 +166,7 @@ public class PowerStatsSchedulerTest { // Skip the first entry, which was placed in the store at the beginning of this test PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0); PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0); - assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123); + assertThat(timeFrame1.startMonotonicTime).isEqualTo(START_REALTIME + 123); assertThat(timeFrame2.startMonotonicTime) .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration); assertThat(Instant.ofEpochMilli(timeFrame2.startTime)) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 4a537dfaf8b3..36d55a48346e 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -621,10 +621,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { }); // TODO (b/291907312): remove feature flag - mSetFlagsRule.enableFlags(android.app.Flags.FLAG_VISIT_RISKY_URIS); - mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR); mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER, - Flags.FLAG_POLITE_NOTIFICATIONS, android.app.Flags.FLAG_MODES_API); + Flags.FLAG_POLITE_NOTIFICATIONS); initNMS(); } @@ -6274,21 +6272,15 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(personIcon3.getUri())); } - private PendingIntent getPendingIntentWithUri(Uri uri) { - return PendingIntent.getActivity(mContext, 0, - new Intent("action", uri), - PendingIntent.FLAG_IMMUTABLE); - } - @Test - public void testVisitUris_callStyle_ongoingCall() { + public void testVisitUris_callStyle() { Icon personIcon = Icon.createWithContentUri("content://media/person"); Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); Person callingPerson = new Person.Builder().setName("Someone") .setIcon(personIcon) .build(); - Uri hangUpUri = Uri.parse("content://intent/hangup"); - PendingIntent hangUpIntent = getPendingIntentWithUri(hangUpUri); + PendingIntent hangUpIntent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); Notification n = new Notification.Builder(mContext, "a") .setStyle(Notification.CallStyle.forOngoingCall(callingPerson, hangUpIntent) .setVerificationIcon(verificationIcon)) @@ -6301,35 +6293,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(visitor, times(1)).accept(eq(personIcon.getUri())); verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); - verify(visitor, times(1)).accept(eq(hangUpUri)); - } - - @Test - public void testVisitUris_callStyle_incomingCall() { - Icon personIcon = Icon.createWithContentUri("content://media/person"); - Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); - Person callingPerson = new Person.Builder().setName("Someone") - .setIcon(personIcon) - .build(); - Uri answerUri = Uri.parse("content://intent/answer"); - PendingIntent answerIntent = getPendingIntentWithUri(answerUri); - Uri declineUri = Uri.parse("content://intent/decline"); - PendingIntent declineIntent = getPendingIntentWithUri(declineUri); - Notification n = new Notification.Builder(mContext, "a") - .setStyle(Notification.CallStyle.forIncomingCall(callingPerson, declineIntent, - answerIntent) - .setVerificationIcon(verificationIcon)) - .setContentTitle("Calling...") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .build(); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - n.visitUris(visitor); - - verify(visitor, times(1)).accept(eq(personIcon.getUri())); - verify(visitor, times(1)).accept(eq(verificationIcon.getUri())); - verify(visitor, times(1)).accept(eq(answerIntent.getIntent().getData())); - verify(visitor, times(1)).accept(eq(declineUri)); } @Test @@ -6381,74 +6344,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris_wearableExtender() { Icon actionIcon = Icon.createWithContentUri("content://media/action"); Icon wearActionIcon = Icon.createWithContentUri("content://media/wearAction"); - Uri displayIntentUri = Uri.parse("content://intent/display"); - PendingIntent displayIntent = getPendingIntentWithUri(displayIntentUri); - Uri actionIntentUri = Uri.parse("content://intent/action"); - PendingIntent actionIntent = getPendingIntentWithUri(actionIntentUri); - Uri wearActionIntentUri = Uri.parse("content://intent/wear"); - PendingIntent wearActionIntent = getPendingIntentWithUri(wearActionIntentUri); + PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent(), + PendingIntent.FLAG_IMMUTABLE); Notification n = new Notification.Builder(mContext, "a") .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addAction( - new Notification.Action.Builder(actionIcon, "Hey!", actionIntent).build()) - .extend(new Notification.WearableExtender() - .setDisplayIntent(displayIntent) - .addAction(new Notification.Action.Builder(wearActionIcon, "Wear!", - wearActionIntent) - .build())) + .addAction(new Notification.Action.Builder(actionIcon, "Hey!", intent).build()) + .extend(new Notification.WearableExtender().addAction( + new Notification.Action.Builder(wearActionIcon, "Wear!", intent).build())) .build(); Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); n.visitUris(visitor); verify(visitor).accept(eq(actionIcon.getUri())); - verify(visitor, times(1)).accept(eq(actionIntentUri)); verify(visitor).accept(eq(wearActionIcon.getUri())); - verify(visitor, times(1)).accept(eq(wearActionIntentUri)); - } - - @Test - public void testVisitUris_tvExtender() { - Uri contentIntentUri = Uri.parse("content://intent/content"); - PendingIntent contentIntent = getPendingIntentWithUri(contentIntentUri); - Uri deleteIntentUri = Uri.parse("content://intent/delete"); - PendingIntent deleteIntent = getPendingIntentWithUri(deleteIntentUri); - Notification n = new Notification.Builder(mContext, "a") - .extend( - new Notification.TvExtender() - .setContentIntent(contentIntent) - .setDeleteIntent(deleteIntent)) - .build(); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - n.visitUris(visitor); - - verify(visitor, times(1)).accept(eq(contentIntentUri)); - verify(visitor, times(1)).accept(eq(deleteIntentUri)); - } - - @Test - public void testVisitUris_carExtender() { - final String testParticipant = "testParticipant"; - Uri readPendingIntentUri = Uri.parse("content://intent/read"); - PendingIntent readPendingIntent = getPendingIntentWithUri(readPendingIntentUri); - Uri replyPendingIntentUri = Uri.parse("content://intent/reply"); - PendingIntent replyPendingIntent = getPendingIntentWithUri(replyPendingIntentUri); - final RemoteInput testRemoteInput = new RemoteInput.Builder("key").build(); - - Notification n = new Notification.Builder(mContext, "a") - .extend(new Notification.CarExtender().setUnreadConversation( - new Notification.CarExtender.Builder(testParticipant) - .setReadPendingIntent(readPendingIntent) - .setReplyAction(replyPendingIntent, testRemoteInput) - .build())) - .build(); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - n.visitUris(visitor); - - verify(visitor, times(1)).accept(eq(readPendingIntentUri)); - verify(visitor, times(1)).accept(eq(replyPendingIntentUri)); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java index ea948ca0e28b..44dbe385a144 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -146,10 +146,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { .put(Notification.Action.Builder.class, "extend") // Overwrites icon supplied to constructor. .put(Notification.BubbleMetadata.Builder.class, "setIcon") - // Overwrites intent supplied to constructor. - .put(Notification.BubbleMetadata.Builder.class, "setIntent") - // Overwrites intent supplied to constructor. - .put(Notification.BubbleMetadata.Builder.class, "setDeleteIntent") // Discards previously-added actions. .put(RemoteViews.class, "mergeRemoteViews") .build(); @@ -684,14 +680,14 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { } if (clazz == Intent.class) { - return new Intent("action", generateUri(where.plus(Intent.class))); + // TODO(b/281044385): Are Intent Uris (new Intent(String,Uri)) relevant? + return new Intent("action"); } if (clazz == PendingIntent.class) { - // PendingIntent can have an Intent with a Uri. - Uri intentUri = generateUri(where.plus(PendingIntent.class)); - return PendingIntent.getActivity(mContext, 0, - new Intent("action", intentUri), + // PendingIntent can have an Intent with a Uri but those are inaccessible and + // not inspected. + return PendingIntent.getActivity(mContext, 0, new Intent("action"), PendingIntent.FLAG_IMMUTABLE); } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java new file mode 100644 index 000000000000..49efd1bdd92a --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 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.vibrator; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Test; + +public class VibratorControlServiceTest { + + private VibratorControlService mVibratorControlService; + private final Object mLock = new Object(); + + @Before + public void setUp() throws Exception { + mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock); + } + + @Test + public void testRegisterVibratorController() throws RemoteException { + FakeVibratorController fakeController = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController); + + assertThat(fakeController.isLinkedToDeath).isTrue(); + } + + @Test + public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest() + throws RemoteException { + FakeVibratorController fakeController = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController); + mVibratorControlService.unregisterVibratorController(fakeController); + assertThat(fakeController.isLinkedToDeath).isFalse(); + } + + @Test + public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest() + throws RemoteException { + FakeVibratorController fakeController1 = new FakeVibratorController(); + FakeVibratorController fakeController2 = new FakeVibratorController(); + mVibratorControlService.registerVibratorController(fakeController1); + + mVibratorControlService.unregisterVibratorController(fakeController2); + assertThat(fakeController1.isLinkedToDeath).isTrue(); + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java new file mode 100644 index 000000000000..79abe21a301d --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 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.vibrator; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.RemoteException; + +import org.junit.Before; +import org.junit.Test; + +public class VibratorControllerHolderTest { + + private final FakeVibratorController mFakeVibratorController = new FakeVibratorController(); + private VibratorControllerHolder mVibratorControllerHolder; + + @Before + public void setUp() throws Exception { + mVibratorControllerHolder = new VibratorControllerHolder(); + } + + @Test + public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + assertThat(mVibratorControllerHolder.getVibratorController()) + .isEqualTo(mFakeVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); + } + + @Test + public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + mVibratorControllerHolder.setVibratorController(null); + assertThat(mFakeVibratorController.isLinkedToDeath).isFalse(); + assertThat(mVibratorControllerHolder.getVibratorController()).isNull(); + } + + @Test + public void testBinderDied_withValidController_unlinksVibratorControllerToDeath() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + mVibratorControllerHolder.binderDied(mFakeVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isFalse(); + assertThat(mVibratorControllerHolder.getVibratorController()).isNull(); + } + + @Test + public void testBinderDied_withInvalidController_ignoresRequest() + throws RemoteException { + mVibratorControllerHolder.setVibratorController(mFakeVibratorController); + FakeVibratorController imposterVibratorController = new FakeVibratorController(); + mVibratorControllerHolder.binderDied(imposterVibratorController); + assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); + assertThat(mVibratorControllerHolder.getVibratorController()) + .isEqualTo(mFakeVibratorController); + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 4e9bbe0a28fe..d6b2116e2682 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -307,9 +307,10 @@ public class VibratorManagerServiceTest { @Override void addService(String name, IBinder service) { - Object serviceInstance = service; - mExternalVibratorService = - (VibratorManagerService.ExternalVibratorService) serviceInstance; + if (service instanceof VibratorManagerService.ExternalVibratorService) { + mExternalVibratorService = + (VibratorManagerService.ExternalVibratorService) service; + } } HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider( diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java new file mode 100644 index 000000000000..7e235870cedc --- /dev/null +++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.annotation.NonNull; +import android.frameworks.vibrator.IVibratorController; +import android.os.IBinder; +import android.os.RemoteException; + +/** + * Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for + * testing. + */ +public final class FakeVibratorController extends IVibratorController.Stub { + + public boolean isLinkedToDeath = false; + + @Override + public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException { + + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } + + @Override + public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { + super.linkToDeath(recipient, flags); + isLinkedToDeath = true; + } + + @Override + public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { + isLinkedToDeath = false; + return super.unlinkToDeath(recipient, flags); + } +} diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp index 744cb63f72b3..8a79fe443179 100644 --- a/services/tests/voiceinteractiontests/Android.bp +++ b/services/tests/voiceinteractiontests/Android.bp @@ -44,6 +44,7 @@ android_test { "servicestests-core-utils", "servicestests-utils-mockito-extended", "truth", + "frameworks-base-testutils", ], libs: [ diff --git a/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java new file mode 100644 index 000000000000..656957c35a5d --- /dev/null +++ b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java @@ -0,0 +1,180 @@ +/* + * 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.voiceinteraction; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.Manifest; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.app.role.RoleManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.PermissionEnforcer; +import android.os.Process; +import android.os.test.FakePermissionEnforcer; +import android.platform.test.annotations.Presubmit; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.modules.utils.testing.ExtendedMockitoRule; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.permission.LegacyPermissionManagerInternal; +import com.android.server.pm.permission.PermissionManagerServiceInternal; +import com.android.server.wm.ActivityTaskManagerInternal; + +import org.junit.Before; +import org.junit.Rule; +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; +import org.mockito.quality.Strictness; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class SetSandboxedTrainingDataAllowedTest { + + @Captor private ArgumentCaptor<Integer> mOpIdCaptor, mUidCaptor, mOpModeCaptor; + + @Mock + private AppOpsManager mAppOpsManager; + + @Mock + private VoiceInteractionManagerServiceImpl mVoiceInteractionManagerServiceImpl; + + private FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer(); + + private Context mContext; + + private VoiceInteractionManagerService mVoiceInteractionManagerService; + private VoiceInteractionManagerService.VoiceInteractionManagerServiceStub + mVoiceInteractionManagerServiceStub; + + private ApplicationInfo mApplicationInfo = new ApplicationInfo(); + + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = + new ExtendedMockitoRule.Builder(this) + .setStrictness(Strictness.WARN) + .mockStatic(LocalServices.class) + .mockStatic(PermissionEnforcer.class) + .build(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(ApplicationProvider.getApplicationContext()); + + doReturn(mPermissionEnforcer).when(() -> PermissionEnforcer.fromContext(any())); + doReturn(mock(PermissionManagerServiceInternal.class)).when( + () -> LocalServices.getService(PermissionManagerServiceInternal.class)); + doReturn(mock(ActivityManagerInternal.class)).when( + () -> LocalServices.getService(ActivityManagerInternal.class)); + doReturn(mock(UserManagerInternal.class)).when( + () -> LocalServices.getService(UserManagerInternal.class)); + doReturn(mock(ActivityTaskManagerInternal.class)).when( + () -> LocalServices.getService(ActivityTaskManagerInternal.class)); + doReturn(mock(LegacyPermissionManagerInternal.class)).when( + () -> LocalServices.getService(LegacyPermissionManagerInternal.class)); + doReturn(mock(RoleManager.class)).when(mContext).getSystemService(RoleManager.class); + doReturn(mAppOpsManager).when(mContext).getSystemService(Context.APP_OPS_SERVICE); + doReturn(mApplicationInfo).when(mVoiceInteractionManagerServiceImpl).getApplicationInfo(); + + mVoiceInteractionManagerService = new VoiceInteractionManagerService(mContext); + mVoiceInteractionManagerServiceStub = + mVoiceInteractionManagerService.new VoiceInteractionManagerServiceStub(); + mVoiceInteractionManagerServiceStub.mImpl = mVoiceInteractionManagerServiceImpl; + mPermissionEnforcer.grant(Manifest.permission.MANAGE_HOTWORD_DETECTION); + } + + @Test + public void setIsReceiveSandboxedTrainingDataAllowed_currentAndPreinstalledAssistant_setsOp() { + // Set application info so current app is the current and preinstalled assistant. + mApplicationInfo.uid = Process.myUid(); + mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed( + /* allowed= */ true); + + verify(mAppOpsManager).setUidMode(mOpIdCaptor.capture(), mUidCaptor.capture(), + mOpModeCaptor.capture()); + assertThat(mOpIdCaptor.getValue()).isEqualTo( + AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA); + assertThat(mOpModeCaptor.getValue()).isEqualTo(AppOpsManager.MODE_ALLOWED); + assertThat(mUidCaptor.getValue()).isEqualTo(Process.myUid()); + } + + @Test + public void setIsReceiveSandboxedTrainingDataAllowed_missingPermission_doesNotSetOp() { + // Set application info so current app is the current and preinstalled assistant. + mApplicationInfo.uid = Process.myUid(); + mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + // Simulate missing MANAGE_HOTWORD_DETECTION permission. + mPermissionEnforcer.revoke(Manifest.permission.MANAGE_HOTWORD_DETECTION); + + assertThrows(SecurityException.class, + () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } + + @Test + public void setIsReceiveSandboxedTrainingDataAllowed_notPreinstalledAssistant_doesNotSetOp() { + // Set application info so current app is not preinstalled assistant. + mApplicationInfo.uid = Process.myUid(); + mApplicationInfo.flags = ApplicationInfo.FLAG_INSTALLED; // Does not contain FLAG_SYSTEM. + + assertThrows(SecurityException.class, + () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } + + @Test + public void setIsReceiveSandboxedTrainingDataAllowed_notCurrentAssistant_doesNotSetOp() { + // Set application info so current app is not current assistant. + mApplicationInfo.uid = Process.SHELL_UID; // Set current assistant uid to shell UID. + mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + assertThrows(SecurityException.class, + () -> mVoiceInteractionManagerServiceStub.setIsReceiveSandboxedTrainingDataAllowed( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 71d2504e1746..dfe79bf1e3e6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -993,7 +993,9 @@ public class DisplayContentTests extends WindowTestsBase { dc.getDisplayPolicy().getDecorInsetsInfo(ROTATION_0, dc.mBaseDisplayHeight, dc.mBaseDisplayWidth).mConfigFrame.set(0, 0, 1000, 990); dc.computeScreenConfiguration(config, ROTATION_0); + dc.onRequestedOverrideConfigurationChanged(config); assertEquals(Configuration.ORIENTATION_LANDSCAPE, config.orientation); + assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getNaturalOrientation()); } @Test diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 3d2340cca378..72db7fecb8ac 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -2278,6 +2278,12 @@ public class UsageStatsService extends SystemService implements "Only the system or holders of the REPORT_USAGE_STATS" + " permission are allowed to call reportUserInteraction"); } + if (userId != UserHandle.getCallingUserId()) { + // Cross-user event reporting. + getContext().enforceCallingPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "Caller doesn't have INTERACT_ACROSS_USERS_FULL permission"); + } } else { if (!isCallingUidSystem()) { throw new SecurityException("Only system is allowed to call" @@ -2287,7 +2293,8 @@ public class UsageStatsService extends SystemService implements // Verify if this package exists before reporting an event for it. if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) { - throw new IllegalArgumentException("Package " + packageName + "not exist!"); + throw new IllegalArgumentException("Package " + packageName + + " does not exist!"); } final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index b214591610de..6e4f13a75625 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -2273,9 +2273,9 @@ public class VoiceInteractionManagerService extends SystemService { private boolean isCallerPreinstalledAssistant() { return mImpl != null - && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid() - && (mImpl.mInfo.getServiceInfo().applicationInfo.isSystemApp() - || mImpl.mInfo.getServiceInfo().applicationInfo.isUpdatedSystemApp()); + && mImpl.getApplicationInfo().uid == Binder.getCallingUid() + && (mImpl.getApplicationInfo().isSystemApp() + || mImpl.getApplicationInfo().isUpdatedSystemApp()); } private void setImplLocked(VoiceInteractionManagerServiceImpl impl) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 3c4b58fa2b69..7e0cbad1e828 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -40,6 +40,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; @@ -540,6 +541,10 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mInfo.getSupportsLocalInteraction(); } + public ApplicationInfo getApplicationInfo() { + return mInfo.getServiceInfo().applicationInfo; + } + public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) { if (DEBUG) { Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index b356fde53417..326b6f5af613 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1508,8 +1508,14 @@ public class SubscriptionManager { public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { if (listener == null) return; - addOnSubscriptionsChangedListener( - new HandlerExecutor(new Handler(listener.getCreatorLooper())), listener); + Looper looper = listener.getCreatorLooper(); + if (looper == null) { + throw new RuntimeException( + "Can't create handler inside thread " + Thread.currentThread() + + " that has not called Looper.prepare()"); + } + + addOnSubscriptionsChangedListener(new HandlerExecutor(new Handler(looper)), listener); } /** diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java index 068dfe8f3d11..a1356237e5a4 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java @@ -100,6 +100,10 @@ public class LongArrayMultiStateCounter_host { mLastStateChangeTimestampMs = timestampMs; } + public void setValue(int state, long[] values) { + System.arraycopy(values, 0, mStates[state].mCounter, 0, mArrayLength); + } + public void updateValue(long[] values, long timestampMs) { if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) { if (timestampMs < mLastStateChangeTimestampMs) { @@ -306,6 +310,11 @@ public class LongArrayMultiStateCounter_host { return getInstance(instanceId).mArrayLength; } + public static void native_setValues(long instanceId, int state, long containerInstanceId) { + getInstance(instanceId).setValue(state, + LongArrayContainer_host.getInstance(containerInstanceId)); + } + public static void native_updateValues(long instanceId, long containerInstanceId, long timestampMs) { getInstance(instanceId).updateValue( diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java index 3bcabcb01c5e..d63bff6f4da3 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java @@ -343,6 +343,28 @@ public class Parcel_host { p.mPos += length; p.updateSize(); } + public static int nativeCompareData(long thisNativePtr, long otherNativePtr) { + var a = getInstance(thisNativePtr); + var b = getInstance(otherNativePtr); + if ((a.mSize == b.mSize) && Arrays.equals(a.mBuffer, b.mBuffer)) { + return 0; + } else { + return -1; + } + } + public static boolean nativeCompareDataInRange( + long ptrA, int offsetA, long ptrB, int offsetB, int length) { + var a = getInstance(ptrA); + var b = getInstance(ptrB); + if (offsetA < 0 || offsetA + length > a.mSize) { + throw new IllegalArgumentException(); + } + if (offsetB < 0 || offsetB + length > b.mSize) { + throw new IllegalArgumentException(); + } + return Arrays.equals(Arrays.copyOfRange(a.mBuffer, offsetA, offsetA + length), + Arrays.copyOfRange(b.mBuffer, offsetB, offsetB + length)); + } public static void nativeAppendFrom( long thisNativePtr, long otherNativePtr, int srcOffset, int length) { var dst = getInstance(thisNativePtr); |