diff options
946 files changed, 22998 insertions, 8159 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 08961ed5b8c1..705a4df6c30f 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -26,7 +26,9 @@ aconfig_srcjars = [ ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", + ":android.server.app.flags-aconfig-java{.generated_srcjars}", ":android.service.chooser.flags-aconfig-java{.generated_srcjars}", + ":android.service.dreams.flags-aconfig-java{.generated_srcjars}", ":android.service.notification.flags-aconfig-java{.generated_srcjars}", ":android.view.flags-aconfig-java{.generated_srcjars}", ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}", @@ -657,7 +659,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } -// CoreNetworking +// Networking +aconfig_declarations { + name: "com.android.net.flags-aconfig", + package: "com.android.net.flags", + srcs: ["core/java/android/net/flags.aconfig"], +} + java_aconfig_library { name: "com.android.net.flags-aconfig-java", aconfig_declarations: "com.android.net.flags-aconfig", @@ -741,6 +749,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Dreams +aconfig_declarations { + name: "android.service.dreams.flags-aconfig", + package: "android.service.dreams", + srcs: ["core/java/android/service/dreams/flags.aconfig"], +} + +java_aconfig_library { + name: "android.service.dreams.flags-aconfig-java", + aconfig_declarations: "android.service.dreams.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Notifications aconfig_declarations { name: "android.service.notification.flags-aconfig", @@ -833,6 +854,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// App +aconfig_declarations { + name: "android.server.app.flags-aconfig", + package: "android.server.app", + srcs: ["services/core/java/com/android/server/app/flags.aconfig"], +} + +java_aconfig_library { + name: "android.server.app.flags-aconfig-java", + aconfig_declarations: "android.server.app.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // WebView aconfig_declarations { name: "android.webkit.flags-aconfig", diff --git a/Android.bp b/Android.bp index fa7c97d3d21a..676a0f51d3f6 100644 --- a/Android.bp +++ b/Android.bp @@ -106,7 +106,7 @@ filegroup { ":android.hardware.radio.voice-V3-java-source", ":android.hardware.security.keymint-V3-java-source", ":android.hardware.security.secureclock-V1-java-source", - ":android.hardware.thermal-V1-java-source", + ":android.hardware.thermal-V2-java-source", ":android.hardware.tv.tuner-V2-java-source", ":android.security.apc-java-source", ":android.security.authorization-java-source", 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/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt index 6d1e6d0cbd73..4352c8ae982e 100644 --- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt @@ -24,10 +24,11 @@ import android.content.pm.parsing.result.ParseTypeImpl import android.content.res.TypedArray import android.perftests.utils.BenchmarkState import android.perftests.utils.PerfStatusReporter +import android.util.ArraySet import androidx.test.filters.LargeTest +import com.android.internal.pm.parsing.pkg.PackageImpl +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils import com.android.internal.util.ConcurrentUtils -import com.android.server.pm.parsing.pkg.PackageImpl -import com.android.server.pm.pkg.parsing.ParsingPackageUtils import java.io.File import java.io.FileOutputStream import java.util.concurrent.ArrayBlockingQueue @@ -214,7 +215,10 @@ public class PackageParsingPerfTest { path, manifestArray, isCoreApp, + this, ) + override fun getHiddenApiWhitelistedApps() = ArraySet<String>() + override fun getInstallConstraintsAllowlist() = ArraySet<String>() }) override fun parseImpl(file: File) = diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java index ad3e422769d5..e3ba50dc635b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -26,6 +26,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.SystemClock; import android.os.UserHandle; @@ -314,9 +315,15 @@ public final class BackgroundJobsController extends StateController { if (mPackageStoppedState.contains(uid, packageName)) { return mPackageStoppedState.get(uid, packageName); } - final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid); - mPackageStoppedState.add(uid, packageName, isStopped); - return isStopped; + + try { + final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid); + mPackageStoppedState.add(uid, packageName, isStopped); + return isStopped; + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Couldn't determine stopped state for unknown package: " + packageName); + return false; + } } @GuardedBy("mLock") diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 1c29982dbd48..8ddbf691359f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -36,6 +36,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.UidObserver; +import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; @@ -772,18 +773,23 @@ public final class QuotaController extends StateController { if (!jobStatus.shouldTreatAsExpeditedJob()) { // If quota is currently "free", then the job can run for the full amount of time, // regardless of bucket (hence using charging instead of isQuotaFreeLocked()). - if (mService.isBatteryCharging() - // The top and foreground cases here were added because apps in those states - // aren't really restricted and the work could be something the user is - // waiting for. Now that user-initiated jobs are a defined concept, we may - // not need these exemptions as much. However, UIJs are currently limited - // (as of UDC) to data transfer work. There may be other work that could - // rely on this exception. Once we add more UIJ types, we can re-evaluate - // the need for these exceptions. - // TODO: re-evaluate the need for these exceptions - || mTopAppCache.get(jobStatus.getSourceUid()) + if (mService.isBatteryCharging()) { + return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; + } + // The top and foreground cases here were added because apps in those states + // aren't really restricted and the work could be something the user is + // waiting for. Now that user-initiated jobs are a defined concept, we may + // not need these exemptions as much. However, UIJs are currently limited + // (as of UDC) to data transfer work. There may be other work that could + // rely on this exception. Once we add more UIJ types, we can re-evaluate + // the need for these exceptions. + // TODO: re-evaluate the need for these exceptions + final boolean isInPrivilegedState = mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus) - || isUidInForeground(jobStatus.getSourceUid())) { + || isUidInForeground(jobStatus.getSourceUid()); + final boolean isJobImportant = jobStatus.getEffectivePriority() >= JobInfo.PRIORITY_HIGH + || (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0; + if (isInPrivilegedState && isJobImportant) { return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; } return getTimeUntilQuotaConsumedLocked( @@ -2549,7 +2555,25 @@ public final class QuotaController extends StateController { */ @Override public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) { - mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget(); + // Skip posting a message to the handler for events we don't care about. + switch (event.getEventType()) { + case UsageEvents.Event.ACTIVITY_RESUMED: + case UsageEvents.Event.ACTIVITY_PAUSED: + case UsageEvents.Event.ACTIVITY_STOPPED: + case UsageEvents.Event.ACTIVITY_DESTROYED: + case UsageEvents.Event.USER_INTERACTION: + case UsageEvents.Event.CHOOSER_ACTION: + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + case UsageEvents.Event.NOTIFICATION_SEEN: + mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event) + .sendToTarget(); + break; + default: + if (DEBUG) { + Slog.d(TAG, "Dropping event " + event.getEventType()); + } + break; + } } } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index b8397d2cd1b4..357e139617ef 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -316,8 +316,25 @@ public class InternalResourceService extends SystemService { */ @Override public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) { - mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event) - .sendToTarget(); + // Skip posting a message to the handler for events we don't care about. + switch (event.getEventType()) { + case UsageEvents.Event.ACTIVITY_RESUMED: + case UsageEvents.Event.ACTIVITY_PAUSED: + case UsageEvents.Event.ACTIVITY_STOPPED: + case UsageEvents.Event.ACTIVITY_DESTROYED: + case UsageEvents.Event.USER_INTERACTION: + case UsageEvents.Event.CHOOSER_ACTION: + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + case UsageEvents.Event.NOTIFICATION_SEEN: + mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event) + .sendToTarget(); + break; + default: + if (DEBUG) { + Slog.d(TAG, "Dropping event " + event.getEventType()); + } + break; + } } }; 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/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt index 9ffb70496c59..43caaecebdaf 100644 --- a/api/coverage/tools/ExtractFlaggedApis.kt +++ b/api/coverage/tools/ExtractFlaggedApis.kt @@ -23,35 +23,43 @@ import java.io.FileWriter /** Usage: extract-flagged-apis <api text file> <output .pb file> */ fun main(args: Array<String>) { var cb = ApiFile.parseApi(listOf(File(args[0]))) - val flagToApi = mutableMapOf<String, MutableList<String>>() - cb.getPackages() - .allClasses() - .filter { it.methods().size > 0 } - .forEach { - for (method in it.methods()) { - val flagValue = - method.modifiers - .findAnnotation("android.annotation.FlaggedApi") - ?.findAttribute("value") - ?.value - ?.value() - if (flagValue != null && flagValue is String) { - val methodQualifiedName = "${it.qualifiedName()}.${method.name()}" - if (flagToApi.containsKey(flagValue)) { - flagToApi.get(flagValue)?.add(methodQualifiedName) - } else { - flagToApi.put(flagValue, mutableListOf(methodQualifiedName)) + var builder = FlagApiMap.newBuilder() + for (pkg in cb.getPackages().packages) { + var packageName = pkg.qualifiedName() + pkg.allClasses() + .filter { it.methods().size > 0 } + .forEach { + for (method in it.methods()) { + val flagValue = + method.modifiers + .findAnnotation("android.annotation.FlaggedApi") + ?.findAttribute("value") + ?.value + ?.value() + if (flagValue != null && flagValue is String) { + var api = + JavaMethod.newBuilder() + .setPackageName(packageName) + .setClassName(it.fullName()) + .setMethodName(method.name()) + for (param in method.parameters()) { + api.addParameterTypes(param.type().toTypeString()) + } + if (builder.containsFlagToApi(flagValue)) { + var updatedApis = + builder + .getFlagToApiOrThrow(flagValue) + .toBuilder() + .addJavaMethods(api) + .build() + builder.putFlagToApi(flagValue, updatedApis) + } else { + var apis = FlaggedApis.newBuilder().addJavaMethods(api).build() + builder.putFlagToApi(flagValue, apis) + } } } } - } - var builder = FlagApiMap.newBuilder() - for (flag in flagToApi.keys) { - var flaggedApis = FlaggedApis.newBuilder() - for (method in flagToApi.get(flag).orEmpty()) { - flaggedApis.addFlaggedApi(FlaggedApi.newBuilder().setQualifiedName(method)) - } - builder.putFlagToApi(flag, flaggedApis.build()) } val flagApiMap = builder.build() FileWriter(args[1]).use { it.write(flagApiMap.toString()) } diff --git a/api/coverage/tools/extract_flagged_apis.proto b/api/coverage/tools/extract_flagged_apis.proto index a858108a27b2..031d621b178f 100644 --- a/api/coverage/tools/extract_flagged_apis.proto +++ b/api/coverage/tools/extract_flagged_apis.proto @@ -25,10 +25,13 @@ message FlagApiMap { } message FlaggedApis { - repeated FlaggedApi flagged_api = 1; + repeated JavaMethod java_methods = 1; } -message FlaggedApi { - string qualified_name = 1; +message JavaMethod { + string package_name = 1; + string class_name = 2; + string method_name = 3; + repeated string parameter_types = 4; } 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 61c7ec6411c6..7f261d450b42 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1216,6 +1216,7 @@ package android { field public static final int opticalInsetLeft = 16844168; // 0x1010588 field public static final int opticalInsetRight = 16844170; // 0x101058a field public static final int opticalInsetTop = 16844169; // 0x1010589 + field @FlaggedApi("android.content.pm.sdk_lib_independence") public static final int optional; field public static final int order = 16843242; // 0x10101ea field public static final int orderInCategory = 16843231; // 0x10101df field public static final int ordering = 16843490; // 0x10102e2 @@ -4467,7 +4468,7 @@ package android.app { method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int); method public android.net.Uri onProvideReferrer(); method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int); method @CallSuper protected void onRestart(); method protected void onRestoreInstanceState(@NonNull android.os.Bundle); method public void onRestoreInstanceState(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle); @@ -4509,7 +4510,7 @@ package android.app { method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent); method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>); method public final void requestPermissions(@NonNull String[], int); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public final void requestPermissions(@NonNull String[], int, int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public final void requestPermissions(@NonNull String[], int, int); method public final void requestShowKeyboardShortcuts(); method @Deprecated public boolean requestVisibleBehind(boolean); method public final boolean requestWindowFeature(int); @@ -4557,7 +4558,7 @@ package android.app { method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean shouldDockBigOverlays(); method public boolean shouldShowRequestPermissionRationale(@NonNull String); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public boolean shouldShowRequestPermissionRationale(@NonNull String, int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public boolean shouldShowRequestPermissionRationale(@NonNull String, int); method public boolean shouldUpRecreateTask(android.content.Intent); method public boolean showAssist(android.os.Bundle); method @Deprecated public final void showDialog(int); @@ -5284,9 +5285,10 @@ package android.app { field public static final int START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START = 21; // 0x15 field public static final int START_TIMESTAMP_RESERVED_RANGE_SYSTEM = 20; // 0x14 field public static final int START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE = 7; // 0x7 - field public static final int START_TYPE_COLD = 0; // 0x0 - field public static final int START_TYPE_HOT = 2; // 0x2 - field public static final int START_TYPE_WARM = 1; // 0x1 + field public static final int START_TYPE_COLD = 1; // 0x1 + field public static final int START_TYPE_HOT = 3; // 0x3 + field public static final int START_TYPE_UNSET = 0; // 0x0 + field public static final int START_TYPE_WARM = 2; // 0x2 } public final class AsyncNotedAppOp implements android.os.Parcelable { @@ -9838,7 +9840,7 @@ package android.content { method public int describeContents(); method public void enforceCallingUid(); method @Nullable public String getAttributionTag(); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public int getDeviceId(); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public int getDeviceId(); method @Nullable public android.content.AttributionSource getNext(); method @Nullable public String getPackageName(); method public int getPid(); @@ -9854,7 +9856,7 @@ package android.content { ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource build(); method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull public android.content.AttributionSource.Builder setDeviceId(int); method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String); @@ -36910,6 +36912,7 @@ package android.provider { field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS"; field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA"; + field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL"; field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM"; field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE"; field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; @@ -39069,6 +39072,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); } @@ -44719,12 +44723,12 @@ package android.telephony { field public static final int SCAN_TYPE_PERIODIC = 1; // 0x1 } - public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher { - ctor public PhoneNumberFormattingTextWatcher(); - ctor public PhoneNumberFormattingTextWatcher(String); - method public void afterTextChanged(android.text.Editable); - method public void beforeTextChanged(CharSequence, int, int, int); - method public void onTextChanged(CharSequence, int, int, int); + @Deprecated public class PhoneNumberFormattingTextWatcher implements android.text.TextWatcher { + ctor @Deprecated public PhoneNumberFormattingTextWatcher(); + ctor @Deprecated @WorkerThread public PhoneNumberFormattingTextWatcher(String); + method @Deprecated public void afterTextChanged(android.text.Editable); + method @Deprecated public void beforeTextChanged(CharSequence, int, int, int); + method @Deprecated public void onTextChanged(CharSequence, int, int, int); } public class PhoneNumberUtils { @@ -53879,9 +53883,11 @@ package android.view { method @Deprecated public android.view.Display getDefaultDisplay(); method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics(); method public default boolean isCrossWindowBlurEnabled(); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer); method public void removeViewImmediate(android.view.View); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"; field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"; field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; @@ -60862,6 +60868,16 @@ package android.window { method public void markSyncReady(); } + @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable { + ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents(); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @NonNull public static final android.os.Parcelable.Creator<android.window.TrustedPresentationThresholds> CREATOR; + field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minAlpha; + field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minFractionRendered; + field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @IntRange(from=1) public final int stabilityRequirementMs; + } + } package javax.microedition.khronos.egl { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index aeac7bf0a51c..e92564b5d7c2 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -656,6 +656,7 @@ package android.app { field public static final String OPSTR_PLAY_AUDIO = "android:play_audio"; field public static final String OPSTR_POST_NOTIFICATION = "android:post_notification"; field public static final String OPSTR_PROJECT_MEDIA = "android:project_media"; + field @FlaggedApi("android.view.contentprotection.flags.rapid_clear_notifications_by_listener_app_op_enabled") public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = "android:rapid_clear_notifications_by_listener"; field public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard"; field public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms"; field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio"; @@ -3232,6 +3233,7 @@ package android.companion.virtual { method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName); method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int); + method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDisplayImePolicy(int, int); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); } @@ -4008,7 +4010,7 @@ package android.content.pm { field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc field public static final int DELETE_KEEP_DATA = 1; // 0x1 field public static final int DELETE_SUCCEEDED = 1; // 0x1 - field @FlaggedApi("android.permission.flags.device_aware_permission_apis") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID"; + field @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID"; field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; @@ -4089,7 +4091,7 @@ package android.content.pm { field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1 field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff field public static final int MATCH_ANY_USER = 4194304; // 0x400000 - field @Deprecated public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 + field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 field @FlaggedApi("android.content.pm.fix_duplicated_flags") public static final long MATCH_CLONE_PROFILE_LONG = 17179869184L; // 0x400000000L field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000 @@ -4114,7 +4116,7 @@ package android.content.pm { public static interface PackageManager.OnPermissionsChangedListener { method public void onPermissionsChanged(int); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public default void onPermissionsChanged(int, @NonNull String); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onPermissionsChanged(int, @NonNull String); } public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable { @@ -10926,13 +10928,13 @@ package android.permission { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable); method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int); method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>); method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable); method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); @@ -12928,6 +12930,7 @@ package android.service.voice { field public static final int CONFIDENCE_LEVEL_LOW = 1; // 0x1 field public static final int CONFIDENCE_LEVEL_MEDIUM = 2; // 0x2 field public static final int CONFIDENCE_LEVEL_NONE = 0; // 0x0 + field @FlaggedApi("android.service.voice.flags.allow_hotword_bump_egress") public static final int CONFIDENCE_LEVEL_VERY_HIGH = 4; // 0x4 field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordRejectedResult> CREATOR; } @@ -13049,7 +13052,7 @@ package android.service.voice { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.HotwordDetector.Callback); method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.VisualQueryDetector createVisualQueryDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VisualQueryDetector.Callback); - method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public void setIsReceiveSandboxedTrainingDataAllowed(boolean); + method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public void setShouldReceiveSandboxedTrainingData(boolean); } } @@ -13699,6 +13702,7 @@ package android.telephony { method @NonNull public java.util.List<java.lang.Boolean> areCarrierIdentifiersAllowed(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>); method public int describeContents(); method @NonNull public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_restriction_status") public int getCarrierRestrictionStatus(); method public int getDefaultCarrierRestriction(); method @NonNull public java.util.List<android.service.carrier.CarrierIdentifier> getExcludedCarriers(); method public int getMultiSimPolicy(); @@ -14521,6 +14525,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void clearRadioPowerOffForReason(int); method public void dial(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean disableDataConnectivity(); + method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableCellularIdentifierDisclosureNotifications(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableDataConnectivity(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableModemForSlot(int, boolean); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void enableVideoCalling(boolean); @@ -14593,6 +14598,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int); + method @FlaggedApi("com.android.internal.telephony.flags.enable_identifier_disclosure_transparency") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCellularIdentifierDisclosureNotificationEnabled(); method public boolean isDataConnectivityPossible(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled(); @@ -16983,7 +16989,7 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException; method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback); @@ -16995,7 +17001,7 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); @@ -17178,6 +17184,9 @@ package android.view { method @NonNull public default java.util.List<android.content.ComponentName> notifyScreenshotListeners(int); method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback); method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback); + field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1 + field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2 + field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0 } public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable { diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index dec1ee52712d..cef11bb42c3f 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -1909,6 +1909,8 @@ SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilit SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addAccessibilityStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler): SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addTouchExplorationStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions +SamShouldBeLast: android.view.inputmethod.InputMethodInfo#dump(android.util.Printer, String): + SAM-compatible parameters (such as parameter 1, "pw", in android.view.inputmethod.InputMethodInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams): SAM-compatible parameters (such as parameter 2, "filePathCallback", in android.webkit.WebChromeClient.onShowFileChooser) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3f2376db8652..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); } } @@ -903,7 +906,7 @@ package android.content { ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); - ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); + ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); method public void enforceCallingPid(); } @@ -3619,9 +3622,6 @@ package android.view { method public default void setShouldShowWithInsecureKeyguard(int, boolean); method public default boolean shouldShowSystemDecors(int); method @Nullable public default android.graphics.Bitmap snapshotTaskForRecents(@IntRange(from=0) int); - field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1 - field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2 - field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0 field public static final int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600; // 0x258 } 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/accessibilityservice/AccessibilityTrace.java b/core/java/android/accessibilityservice/AccessibilityTrace.java index 7700b33253fd..87304c8311bd 100644 --- a/core/java/android/accessibilityservice/AccessibilityTrace.java +++ b/core/java/android/accessibilityservice/AccessibilityTrace.java @@ -36,7 +36,7 @@ public interface AccessibilityTrace { "IAccessibilityInteractionConnectionCallback"; String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback"; String NAME_MAGNIFICATION_CONNECTION = "IMagnificationConnection"; - String NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = "IWindowMagnificationConnectionCallback"; + String NAME_MAGNIFICATION_CONNECTION_CALLBACK = "IMagnificationConnectionCallback"; String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal"; String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback"; String NAME_MAGNIFICATION_CALLBACK = "MagnificationCallbacks"; @@ -59,7 +59,7 @@ public interface AccessibilityTrace { long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L; long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L; long FLAGS_MAGNIFICATION_CONNECTION = 0x0000000000000080L; - long FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L; + long FLAGS_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L; long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L; long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L; long FLAGS_MAGNIFICATION_CALLBACK = 0x0000000000000800L; @@ -100,8 +100,8 @@ public interface AccessibilityTrace { new AbstractMap.SimpleEntry<String, Long>( NAME_MAGNIFICATION_CONNECTION, FLAGS_MAGNIFICATION_CONNECTION), new AbstractMap.SimpleEntry<String, Long>( - NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK), + NAME_MAGNIFICATION_CONNECTION_CALLBACK, + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK), new AbstractMap.SimpleEntry<String, Long>( NAME_WINDOW_MANAGER_INTERNAL, FLAGS_WINDOW_MANAGER_INTERNAL), new AbstractMap.SimpleEntry<String, Long>( diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index cb08dad85808..4a9fa9e63bf9 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5572,7 +5572,7 @@ public class Activity extends ContextThemeWrapper * @see #shouldShowRequestPermissionRationale * @see Context#DEVICE_ID_DEFAULT */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public final void requestPermissions(@NonNull String[] permissions, int requestCode, int deviceId) { if (requestCode < 0) { @@ -5645,7 +5645,7 @@ public class Activity extends ContextThemeWrapper * * @see #requestPermissions */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, int deviceId) { onRequestPermissionsResult(requestCode, permissions, grantResults); @@ -5678,7 +5678,7 @@ public class Activity extends ContextThemeWrapper * @see #requestPermissions * @see #onRequestPermissionsResult */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) { final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager() : createDeviceContext(deviceId).getPackageManager(); @@ -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/ActivityThread.java b/core/java/android/app/ActivityThread.java index adaaee200895..6ddb36a72aa9 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1544,11 +1544,12 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin, boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, - boolean dumpUnreachable, String[] args) { + boolean dumpUnreachable, boolean dumpAllocatorStats, String[] args) { FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(fout); try { - dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable); + dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, + dumpUnreachable, dumpAllocatorStats); } finally { pw.flush(); IoUtils.closeQuietly(pfd); @@ -1557,7 +1558,8 @@ public final class ActivityThread extends ClientTransactionHandler @NeverCompile private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, - boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable) { + boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, + boolean dumpUnreachable, boolean dumpAllocatorStats) { long nativeMax = Debug.getNativeHeapSize() / 1024; long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; long nativeFree = Debug.getNativeHeapFreeSize() / 1024; @@ -1710,6 +1712,9 @@ public final class ActivityThread extends ClientTransactionHandler pw.println(" Unreachable memory"); pw.print(Debug.getUnreachableMemory(100, showContents)); } + if (dumpAllocatorStats) { + Debug.logAllocatorStats(); + } } @NeverCompile @@ -4072,7 +4077,7 @@ public final class ActivityThread extends ClientTransactionHandler final LoadedApk sdkApk = getPackageInfo( contextInfo.getSdkApplicationInfo(), r.packageInfo.getCompatibilityInfo(), - ActivityContextInfo.CONTEXT_FLAGS); + contextInfo.getContextFlags()); final ContextImpl activityContext = ContextImpl.createActivityContext( this, sdkApk, r.activityInfo, r.token, displayId, r.overrideConfig); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ec43184bd42c..4b24b1f2b2a1 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -18,6 +18,7 @@ package android.app; import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER; import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED; +import static android.view.contentprotection.flags.Flags.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED; import static java.lang.Long.max; @@ -1508,6 +1509,7 @@ public class AppOpsManager { */ public static final int OP_CREATE_ACCESSIBILITY_OVERLAY = AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY; + /** * Indicate that the user has enabled or disabled mobile data * @hide @@ -1529,9 +1531,17 @@ public class AppOpsManager { */ public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING; + /** + * Rapid clearing of notifications by a notification listener, see b/289080543 for details + * + * @hide + */ + public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = + AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 142; + public static final int _NUM_OP = 143; /** * All app ops represented as strings. @@ -1680,6 +1690,7 @@ public class AppOpsManager { OPSTR_MEDIA_ROUTING_CONTROL, OPSTR_ENABLE_MOBILE_DATA_BY_USER, OPSTR_RESERVED_FOR_TESTING, + OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, }) public @interface AppOpString {} @@ -2330,6 +2341,7 @@ public class AppOpsManager { @FlaggedApi(FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED) public static final String OPSTR_CREATE_ACCESSIBILITY_OVERLAY = "android:create_accessibility_overlay"; + /** * Indicate that the user has enabled or disabled mobile data * @hide @@ -2350,6 +2362,16 @@ public class AppOpsManager { public static final String OPSTR_RESERVED_FOR_TESTING = "android:reserved_for_testing"; + /** + * Rapid clearing of notifications by a notification listener, see b/289080543 for details + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED) + public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = + "android:rapid_clear_notifications_by_listener"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2909,6 +2931,10 @@ public class AppOpsManager { "ENABLE_MOBILE_DATA_BY_USER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_RESERVED_FOR_TESTING, OPSTR_RESERVED_FOR_TESTING, "OP_RESERVED_FOR_TESTING").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, + OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, + "RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER") + .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index c8317c8faad5..656feb0401d6 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -104,14 +104,17 @@ public final class ApplicationStartInfo implements Parcelable { /** Process started due to Activity started for any reason not explicitly listed. */ public static final int START_REASON_START_ACTIVITY = 11; + /** Start type not yet set. */ + public static final int START_TYPE_UNSET = 0; + /** Process started from scratch. */ - public static final int START_TYPE_COLD = 0; + public static final int START_TYPE_COLD = 1; /** Process retained minimally SavedInstanceState. */ - public static final int START_TYPE_WARM = 1; + public static final int START_TYPE_WARM = 2; /** Process brought back to foreground. */ - public static final int START_TYPE_HOT = 2; + public static final int START_TYPE_HOT = 3; /** * Default. The system always creates a new instance of the activity in the target task and @@ -277,6 +280,7 @@ public final class ApplicationStartInfo implements Parcelable { @IntDef( prefix = {"START_TYPE_"}, value = { + START_TYPE_UNSET, START_TYPE_COLD, START_TYPE_WARM, START_TYPE_HOT, @@ -769,6 +773,7 @@ public final class ApplicationStartInfo implements Parcelable { private static String startTypeToString(@StartType int startType) { return switch (startType) { + case START_TYPE_UNSET -> "UNSET"; case START_TYPE_COLD -> "COLD"; case START_TYPE_WARM -> "WARM"; case START_TYPE_HOT -> "HOT"; diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index d93544972e7a..53a21cdd78c9 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -16,6 +16,8 @@ package android.app; +import static com.android.internal.util.Preconditions.checkArgument; + import android.annotation.DrawableRes; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -390,7 +392,7 @@ public final class AutomaticZenRule implements Parcelable { */ @FlaggedApi(Flags.FLAG_MODES_API) public void setType(@Type int type) { - mType = type; + mType = checkValidType(type); } /** @@ -451,6 +453,24 @@ public final class AutomaticZenRule implements Parcelable { mAllowManualInvocation = allowManualInvocation; } + /** @hide */ + @FlaggedApi(Flags.FLAG_MODES_API) + public void validate() { + if (Flags.modesApi()) { + checkValidType(mType); + } + } + + @FlaggedApi(Flags.FLAG_MODES_API) + @Type + private static int checkValidType(@Type int type) { + checkArgument(type >= TYPE_UNKNOWN && type <= TYPE_MANAGED, + "Rule type must be one of TYPE_UNKNOWN, TYPE_OTHER, TYPE_SCHEDULE_TIME, " + + "TYPE_SCHEDULE_CALENDAR, TYPE_BEDTIME, TYPE_DRIVING, TYPE_IMMERSIVE, " + + "TYPE_THEATER, or TYPE_MANAGED"); + return type; + } + @Override public int describeContents() { return 0; @@ -703,10 +723,10 @@ public final class AutomaticZenRule implements Parcelable { } /** - * Sets the type of the rule + * Sets the type of the rule. */ public @NonNull Builder setType(@Type int type) { - mType = type; + mType = checkValidType(type); return this; } @@ -714,7 +734,7 @@ public final class AutomaticZenRule implements Parcelable { * Sets a user visible description of when this rule will be active * (see {@link Condition#STATE_TRUE}). * - * A description should be a (localized) string like "Mon-Fri, 9pm-7am" or + * <p>A description should be a (localized) string like "Mon-Fri, 9pm-7am" or * "When connected to [Car Name]". */ public @NonNull Builder setTriggerDescription(@Nullable String description) { diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 75d8c1012e27..5541e7aef160 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -129,7 +129,7 @@ oneway interface IApplicationThread { void scheduleTrimMemory(int level); void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin, boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable, - in String[] args); + boolean dumpAllocatorLogs, in String[] args); void dumpMemInfoProto(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable, in String[] args); diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl index 9a818e49ae4a..bfec9430fd58 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -52,4 +52,6 @@ interface IGameManagerService { void removeGameModeListener(IGameModeListener gameModeListener); void addGameStateListener(IGameStateListener gameStateListener); void removeGameStateListener(IGameStateListener gameStateListener); + @EnforcePermission("MANAGE_GAME_MODE") + void toggleGameDefaultFrameRate(boolean isEnabled); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1e538c52e635..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) { @@ -16640,6 +16640,7 @@ public class DevicePolicyManager { == DEVICE_OWNER_TYPE_FINANCED; } + // TODO(b/315298076): revert ag/25574027 and update the doc /** * Called by a device owner or profile owner of an organization-owned managed profile to enable * or disable USB data signaling for the device. When disabled, USB data connections @@ -16649,12 +16650,11 @@ public class DevicePolicyManager { * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data * signaling is supported on the device. * - * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the USB data signaling + * Starting from Android 15, after the USB data signaling * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was * successfully set or not. This callback will contain: * <ul> - * li> The policy identifier {@link DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY} * <li> The {@link TargetUser} that this policy relates to * <li> The {@link PolicyUpdateResult}, which will be * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 6e451479c5a4..4cf9fcab9092 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -16,6 +16,8 @@ package android.appwidget; +import static android.appwidget.flags.Flags.remoteAdapterConversion; + import android.annotation.BroadcastBehavior; import android.annotation.NonNull; import android.annotation.Nullable; @@ -566,11 +568,9 @@ public class AppWidgetManager { private void tryAdapterConversion( FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action, RemoteViews original, String failureMsg) { - final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled() + if (remoteAdapterConversion() && (mHasPostedLegacyLists = mHasPostedLegacyLists - || (original != null && original.hasLegacyLists())); - - if (isConvertingAdapter) { + || (original != null && original.hasLegacyLists()))) { final RemoteViews viewsCopy = new RemoteViews(original); Runnable updateWidgetWithTask = () -> { try { @@ -587,13 +587,12 @@ public class AppWidgetManager { } updateWidgetWithTask.run(); - return; - } - - try { - action.acceptOrThrow(original); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + } else { + try { + action.acceptOrThrow(original); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } } } @@ -838,22 +837,20 @@ public class AppWidgetManager { return; } - if (!RemoteViews.isAdapterConversionEnabled()) { + if (remoteAdapterConversion()) { + if (Looper.myLooper() == Looper.getMainLooper()) { + mHasPostedLegacyLists = true; + createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange( + appWidgetIds, viewId)); + } else { + notifyCollectionWidgetChange(appWidgetIds, viewId); + } + } else { try { mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } - - return; - } - - if (Looper.myLooper() == Looper.getMainLooper()) { - mHasPostedLegacyLists = true; - createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds, - viewId)); - } else { - notifyCollectionWidgetChange(appWidgetIds, viewId); } } diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 6a735a418b58..c95b864c08bb 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "Enable support for generated previews in AppWidgetManager" bug: "306546610" } + +flag { + name: "remote_adapter_conversion" + namespace: "app_widgets" + description: "Enable adapter conversion to RemoteCollectionItemsAdapter" + bug: "245950570" +}
\ No newline at end of file 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/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 3520c0b4d724..12229b12fb16 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -219,6 +219,10 @@ interface IVirtualDevice { @EnforcePermission("CREATE_VIRTUAL_DEVICE") void setShowPointerIcon(boolean showPointerIcon); + /** Sets an IME policy for the given display. */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + void setDisplayImePolicy(int displayId, int policy); + /** * Registers an intent interceptor that will intercept an intent attempting to launch * when matching the provided IntentFilter and calls the callback with the intercepted diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index 003dffb1f9c1..2abeeeecc1c6 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -53,6 +53,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.ArrayMap; +import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; @@ -361,6 +362,14 @@ public class VirtualDeviceInternal { } } + void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { + try { + mVirtualDevice.setDisplayImePolicy(displayId, policy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + void addActivityListener( @CallbackExecutor @NonNull Executor executor, @NonNull VirtualDeviceManager.ActivityListener listener) { diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 41c90b96dc84..eef60f11fb1c 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -63,6 +63,7 @@ import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.view.Surface; +import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; @@ -914,6 +915,24 @@ public final class VirtualDeviceManager { } /** + * Specifies the IME behavior on the given display. By default, all displays created by + * virtual devices have {@link WindowManager#DISPLAY_IME_POLICY_LOCAL}. + * + * @param displayId the ID of the display to change the IME policy for. It must be owned by + * this virtual device. + * @param policy the IME policy to use on that display + * @throws SecurityException if the display is not owned by this device or is not + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted} + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) + public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { + if (Flags.vdmCustomIme()) { + mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy); + } + } + + /** * Adds an activity listener to listen for events such as top activity change or virtual * display task stack became empty. * diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index b2074a6e7309..a1357c91b2cf 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -173,7 +173,7 @@ public final class AttributionSource implements Parcelable { /** @hide */ @TestApi - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public AttributionSource(int uid, int pid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token, @Nullable String[] renouncedPermissions, @@ -539,7 +539,7 @@ public final class AttributionSource implements Parcelable { * <p> * This device ID is used for permissions checking during attribution source validation. */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public int getDeviceId() { return mAttributionSourceState.deviceId; } @@ -727,7 +727,7 @@ public final class AttributionSource implements Parcelable { * * @return the builder */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public @NonNull Builder setDeviceId(int deviceId) { checkNotUsed(); mBuilderFieldsSet |= 0x12; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 23a5d4d20a2e..7af0be3b3e75 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2888,7 +2888,7 @@ public class Intent implements Parcelable, Cloneable { * <p class="note">This is a protected intent that can only be sent * by the system. * <p> - * Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, an extra timestamp + * Starting in Android V, an extra timestamp * {@link #EXTRA_TIME} is included with this broadcast to indicate the exact time the package * was restarted, in {@link SystemClock#elapsedRealtime() elapsed realtime}. * </p> diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index e9f419e9a8ce..6f7299aa8e31 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -64,6 +64,7 @@ interface ILauncherApps { PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component, in UserHandle user); LauncherUserInfo getLauncherUserInfo(in UserHandle user); + List<String> getPreInstalledSystemPackages(in UserHandle user); void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage, String callingFeatureId, in ComponentName component, in Rect sourceBounds, in Bundle opts, in UserHandle user); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 0cd4358b2c91..ccc8f0956865 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -800,6 +800,29 @@ public class LauncherApps { } /** + * Returns the list of the system packages that are installed at user creation. + * + * <p>An empty list denotes that all system packages are installed for that user at creation. + * This behaviour is inherited from the underlining UserManager API. + * + * @param userHandle the user for which installed system packages are required. + * @return {@link List} of {@link String}, representing the package name of the installed + * package. Can be empty but not null. + * @hide + */ + @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + public List<String> getPreInstalledSystemPackages(@NonNull UserHandle userHandle) { + if (DEBUG) { + Log.i(TAG, "getPreInstalledSystemPackages for user: " + userHandle); + } + try { + return mService.getPreInstalledSystemPackages(userHandle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it * returns null. * diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 5dee65b62201..4f61613b9c6e 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -231,7 +231,7 @@ public class PackageInfo implements Parcelable { * or null if there were none. This is only filled in if the flag * {@link PackageManager#GET_PERMISSIONS} was set. Each value matches * the corresponding entry in {@link #requestedPermissions}, and will have - * the flags {@link #REQUESTED_PERMISSION_GRANTED} and + * the flags {@link #REQUESTED_PERMISSION_GRANTED}, {@link #REQUESTED_PERMISSION_IMPLICIT}, and * {@link #REQUESTED_PERMISSION_NEVER_FOR_LOCATION} set as appropriate. */ @Nullable diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f865a36ea544..a5d16f2f6be1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -731,7 +731,7 @@ public abstract class PackageManager { * @see VirtualDeviceManager.VirtualDevice#getPersistentDeviceId() * @see VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT */ - @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) { Objects.requireNonNull(persistentDeviceId); if (Objects.equals(persistentDeviceId, @@ -1225,12 +1225,10 @@ public abstract class PackageManager { public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO; /** - * @deprecated Use {@link #MATCH_CLONE_PROFILE_LONG} instead. + * Use {@link #MATCH_CLONE_PROFILE_LONG} instead. * * @hide */ - @SuppressLint("UnflaggedApi") // Just adding the @Deprecated annotation - @Deprecated @SystemApi public static final int MATCH_CLONE_PROFILE = 0x20000000; @@ -4893,7 +4891,7 @@ public abstract class PackageManager { * @hide */ @SystemApi - @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID"; @@ -6302,6 +6300,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/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index fdd2aa1fe5fa..25ba72551b04 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -142,8 +142,10 @@ public final class SharedLibraryInfo implements Parcelable { mName = parcel.readString8(); mVersion = parcel.readLong(); mType = parcel.readInt(); - mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class); - mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class); + mDeclaringPackage = + parcel.readParcelable(null, android.content.pm.VersionedPackage.class); + mDependentPackages = + parcel.readArrayList(null, android.content.pm.VersionedPackage.class); mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR); mIsNative = parcel.readBoolean(); } diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index c4012688406d..57749d43eb37 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -68,6 +68,8 @@ public final class UserProperties implements Parcelable { "authAlwaysRequiredToDisableQuietMode"; private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent"; private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible"; + private static final String ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = + "allowStoppingUserWithDelayedLocking"; private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = "crossProfileContentSharingStrategy"; @@ -89,7 +91,8 @@ public final class UserProperties implements Parcelable { INDEX_SHOW_IN_QUIET_MODE, INDEX_SHOW_IN_SHARING_SURFACES, INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE, - INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY + INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY, + INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING, }) @Retention(RetentionPolicy.SOURCE) private @interface PropertyIndex { @@ -110,6 +113,7 @@ public final class UserProperties implements Parcelable { private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13; private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14; private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15; + private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16; /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */ private long mPropertiesPresent = 0; @@ -450,6 +454,7 @@ public final class UserProperties implements Parcelable { setCrossProfileIntentResolutionStrategy(orig.getCrossProfileIntentResolutionStrategy()); setDeleteAppWithParent(orig.getDeleteAppWithParent()); setAlwaysVisible(orig.getAlwaysVisible()); + setAllowStoppingUserWithDelayedLocking(orig.getAllowStoppingUserWithDelayedLocking()); } if (hasManagePermission) { // Add items that require MANAGE_USERS or stronger. @@ -725,6 +730,11 @@ public final class UserProperties implements Parcelable { this.mUpdateCrossProfileIntentFiltersOnOTA = val; setPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA); } + /** + Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during + OTA update between user-parent + */ + private boolean mUpdateCrossProfileIntentFiltersOnOTA; /** * Returns whether a profile shares media with its parent user. @@ -786,12 +796,38 @@ public final class UserProperties implements Parcelable { } private boolean mAuthAlwaysRequiredToDisableQuietMode; - /* - Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during - OTA update between user-parent + /** + * Returns whether a user (usually a profile) is allowed to leave the CE storage unlocked when + * stopped. + * + * <p> Setting this property to true will enable the user's CE storage to remain unlocked when + * the user is stopped using + * {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, + * boolean, IStopUserCallback)}. + * + * <p> When this property is false, delayed locking may still be applicable at a global + * level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed + * locking for a user can happen if either the device configuration is set or if this property + * is set. When both, the config and the property value is false, the user storage is always + * locked when the user is stopped. + * @hide */ - private boolean mUpdateCrossProfileIntentFiltersOnOTA; - + public boolean getAllowStoppingUserWithDelayedLocking() { + if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) { + return mAllowStoppingUserWithDelayedLocking; + } + if (mDefaultProperties != null) { + return mDefaultProperties.mAllowStoppingUserWithDelayedLocking; + } + throw new SecurityException( + "You don't have permission to query allowStoppingUserWithDelayedLocking"); + } + /** @hide */ + public void setAllowStoppingUserWithDelayedLocking(boolean val) { + this.mAllowStoppingUserWithDelayedLocking = val; + setPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING); + } + private boolean mAllowStoppingUserWithDelayedLocking; /** * Returns the user's {@link CrossProfileIntentFilterAccessControlLevel}. @@ -899,6 +935,8 @@ public final class UserProperties implements Parcelable { + ", mCredentialShareableWithParent=" + isCredentialShareableWithParent() + ", mAuthAlwaysRequiredToDisableQuietMode=" + isAuthAlwaysRequiredToDisableQuietMode() + + ", mAllowStoppingUserWithDelayedLocking=" + + getAllowStoppingUserWithDelayedLocking() + ", mDeleteAppWithParent=" + getDeleteAppWithParent() + ", mAlwaysVisible=" + getAlwaysVisible() + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy() @@ -929,6 +967,8 @@ public final class UserProperties implements Parcelable { + isCredentialShareableWithParent()); pw.println(prefix + " mAuthAlwaysRequiredToDisableQuietMode=" + isAuthAlwaysRequiredToDisableQuietMode()); + pw.println(prefix + " mAllowStoppingUserWithDelayedLocking=" + + getAllowStoppingUserWithDelayedLocking()); pw.println(prefix + " mDeleteAppWithParent=" + getDeleteAppWithParent()); pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible()); pw.println(prefix + " mCrossProfileContentSharingStrategy=" @@ -1005,6 +1045,9 @@ public final class UserProperties implements Parcelable { case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE: setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i)); break; + case ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING: + setAllowStoppingUserWithDelayedLocking(parser.getAttributeBoolean(i)); + break; case ATTR_DELETE_APP_WITH_PARENT: setDeleteAppWithParent(parser.getAttributeBoolean(i)); break; @@ -1079,6 +1122,10 @@ public final class UserProperties implements Parcelable { serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE, mAuthAlwaysRequiredToDisableQuietMode); } + if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) { + serializer.attributeBoolean(null, ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING, + mAllowStoppingUserWithDelayedLocking); + } if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) { serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT, mDeleteAppWithParent); @@ -1110,6 +1157,7 @@ public final class UserProperties implements Parcelable { dest.writeBoolean(mMediaSharedWithParent); dest.writeBoolean(mCredentialShareableWithParent); dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode); + dest.writeBoolean(mAllowStoppingUserWithDelayedLocking); dest.writeBoolean(mDeleteAppWithParent); dest.writeBoolean(mAlwaysVisible); dest.writeInt(mCrossProfileContentSharingStrategy); @@ -1136,6 +1184,7 @@ public final class UserProperties implements Parcelable { mMediaSharedWithParent = source.readBoolean(); mCredentialShareableWithParent = source.readBoolean(); mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean(); + mAllowStoppingUserWithDelayedLocking = source.readBoolean(); mDeleteAppWithParent = source.readBoolean(); mAlwaysVisible = source.readBoolean(); mCrossProfileContentSharingStrategy = source.readInt(); @@ -1183,6 +1232,7 @@ public final class UserProperties implements Parcelable { private boolean mMediaSharedWithParent = false; private boolean mCredentialShareableWithParent = false; private boolean mAuthAlwaysRequiredToDisableQuietMode = false; + private boolean mAllowStoppingUserWithDelayedLocking = false; private boolean mDeleteAppWithParent = false; private boolean mAlwaysVisible = false; private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy = @@ -1302,6 +1352,16 @@ public final class UserProperties implements Parcelable { return this; } + /** Sets the value for {@link #mAllowStoppingUserWithDelayedLocking} + * @hide + */ + public Builder setAllowStoppingUserWithDelayedLocking( + boolean allowStoppingUserWithDelayedLocking) { + mAllowStoppingUserWithDelayedLocking = + allowStoppingUserWithDelayedLocking; + return this; + } + /** Sets the value for {@link #mDeleteAppWithParent} * @hide */ @@ -1352,6 +1412,7 @@ public final class UserProperties implements Parcelable { mMediaSharedWithParent, mCredentialShareableWithParent, mAuthAlwaysRequiredToDisableQuietMode, + mAllowStoppingUserWithDelayedLocking, mDeleteAppWithParent, mAlwaysVisible, mCrossProfileContentSharingStrategy); @@ -1372,6 +1433,7 @@ public final class UserProperties implements Parcelable { boolean mediaSharedWithParent, boolean credentialShareableWithParent, boolean authAlwaysRequiredToDisableQuietMode, + boolean allowStoppingUserWithDelayedLocking, boolean deleteAppWithParent, boolean alwaysVisible, @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) { @@ -1390,6 +1452,7 @@ public final class UserProperties implements Parcelable { setCredentialShareableWithParent(credentialShareableWithParent); setAuthAlwaysRequiredToDisableQuietMode( authAlwaysRequiredToDisableQuietMode); + setAllowStoppingUserWithDelayedLocking(allowStoppingUserWithDelayedLocking); setDeleteAppWithParent(deleteAppWithParent); setAlwaysVisible(alwaysVisible); setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy); 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/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index e3a552023e4b..3ab889ddfa5d 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -3317,7 +3317,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * stream configurations are the same as for applications targeting SDK version older than * 31.</p> * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} and - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations } + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">the table</a> * for additional mandatory stream configurations on a per-capability basis.</p> * <p>*1: For JPEG format, the sizes may be restricted by below conditions:</p> * <ul> @@ -3436,11 +3436,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL } * and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }. * This is an app-readable conversion of the mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations guideline} based on specific device level and capabilities. + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">guideline</a>. + * based on specific device level and capabilities. * Clients can use the array as a quick reference to find an appropriate camera stream * combination. * As per documentation, the stream combinations with given PREVIEW, RECORD and @@ -3469,11 +3470,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>An array of mandatory concurrent stream combinations. * This is an app-readable conversion of the concurrent mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline} for each device which has its Id present in the set returned by + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">guideline</a> + * for each device which has its Id present in the set returned by * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds }. * Clients can use the array as a quick reference to find an appropriate camera stream * combination. @@ -3578,7 +3580,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>If a camera device supports multi-resolution output streams for a particular format, for * each of its mandatory stream combinations, the camera device will support using a * MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to - * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs } + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a> * for additional details.</p> * <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }. * A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with @@ -3587,7 +3589,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution * {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported * multi-resolution output stream formats. Refer to - * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }} + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a> * for details about the additional mandatory stream combinations in this case.</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> */ @@ -3700,11 +3702,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set * to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }. * This is an app-readable conversion of the maximum resolution mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors guideline} for each device which has the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">guideline</a> + * for each device which has the * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } * capability. * Clients can use the array as a quick reference to find an appropriate camera stream @@ -3727,11 +3730,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * 10-bit output capability * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } * This is an app-readable conversion of the 10 bit output mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations guideline} for each device which has the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">guideline</a> + * for each device which has the * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } * capability. * Clients can use the array as a quick reference to find an appropriate camera stream @@ -3752,11 +3756,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}. * This is an app-readable conversion of the preview stabilization mandatory stream * combination - * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations guideline} for each device which supports {@code PREVIEW_STABILIZATION} + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">guideline</a> + * for each device which supports {@code PREVIEW_STABILIZATION} * Clients can use the array as a quick reference to find an appropriate camera stream * combination. * The mandatory stream combination array will be {@code null} in case the device does not @@ -3829,8 +3834,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>The guaranteed stream combinations related to stream use case for a camera device with * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } * capability is documented in the camera device - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline}. The application is strongly recommended to use one of the guaranteed stream - * combinations. + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>. + * The application is strongly recommended to use one of the guaranteed stream combinations. * If the application creates a session with a stream combination not in the guaranteed * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session, * the camera device may ignore some stream use cases due to hardware constraints @@ -3866,11 +3871,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>An array of mandatory stream combinations with stream use cases. * This is an app-readable conversion of the mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations tables} with each stream's use case being set.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">tables</a> + * with each stream's use case being set.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline} for a camera device with + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guildeline</a> + * for a camera device with * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } * capability. * The mandatory stream combination array will be {@code null} in case the device doesn't 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/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 002c0b207506..bcce4b65be18 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -1844,7 +1844,7 @@ public final class CameraManager { * Remaps Camera Ids in the CameraService. * * @hide - */ + */ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping) throws CameraAccessException, SecurityException, IllegalArgumentException { @@ -1852,6 +1852,29 @@ public final class CameraManager { } /** + * Injects session params into existing clients in the CameraService. + * + * @param cameraId The camera id of client to inject session params into. + * If no such client exists for cameraId, no injection will + * take place. + * @param sessionParams A {@link CaptureRequest} object containing the + * the sessionParams to inject into the existing client. + * + * @throws CameraAccessException {@link CameraAccessException#CAMERA_DISCONNECTED} will be + * thrown if camera service is not available. Further, if + * if no such client exists for cameraId, + * {@link CameraAccessException#CAMERA_ERROR} will be thrown. + * @throws SecurityException If the caller does not have permission to inject session + * params + * @hide + */ + @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) + public void injectSessionParams(@NonNull String cameraId, @NonNull CaptureRequest sessionParams) + throws CameraAccessException, SecurityException { + CameraManagerGlobal.get().injectSessionParams(cameraId, sessionParams); + } + + /** * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for * currently active session. Validation is done downstream. * @@ -2110,6 +2133,30 @@ public final class CameraManager { } } + /** Injects session params into an existing client for cameraid. */ + public void injectSessionParams(@NonNull String cameraId, + @NonNull CaptureRequest sessionParams) + throws CameraAccessException, SecurityException { + synchronized (mLock) { + ICameraService cameraService = getCameraService(); + if (cameraService == null) { + throw new CameraAccessException( + CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable."); + } + + try { + cameraService.injectSessionParams(cameraId, sessionParams.getNativeMetadata()); + } catch (ServiceSpecificException e) { + throwAsPublicException(e); + } catch (RemoteException e) { + throw new CameraAccessException( + CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable."); + } + } + } + private String[] extractCameraIdListLocked() { String[] cameraIds = null; int idCount = 0; diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 765a8f7c842c..7cf10d89004f 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1216,8 +1216,7 @@ public abstract class CameraMetadata<TKey> { * <ul> * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li> * <li>All mandatory stream combinations for this specific capability as per - * documentation - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations }</li> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li> * <li>In case the device is not able to capture some combination of supported * standard 8-bit and/or 10-bit dynamic range profiles within the same capture request, * then those constraints must be listed in @@ -1256,8 +1255,8 @@ public abstract class CameraMetadata<TKey> { * </ul> * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES } * lists all of the supported stream use cases.</p> - * <p>Refer to - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations } + * <p>Refer to the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a> * for the mandatory stream combinations involving stream use cases, which can also be * queried via {@link android.hardware.camera2.params.MandatoryStreamCombination }.</p> * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES @@ -1756,9 +1755,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or * better.</p> - * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#limited-level-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#limited-level-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic * support for color image capture. The only exception is that the device may * alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth @@ -1784,9 +1783,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device is capable of supporting advanced imaging applications.</p> - * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#full-level-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#full-level-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>A <code>FULL</code> device will support below capabilities:</p> * <ul> * <li><code>BURST_CAPTURE</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains @@ -1814,9 +1813,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device is running in backward compatibility mode.</p> - * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations } - * documentation are supported.</p> + * <p>Only the stream configurations listed in the <code>LEGACY</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">table</a> + * in the documentation are supported.</p> * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual * post-processing, arbitrary cropping regions, and has relaxed performance constraints. * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a @@ -1839,9 +1838,9 @@ public abstract class CameraMetadata<TKey> { * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to * FULL-level capabilities.</p> * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and - * <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#level-3-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#level-3-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>The following additional capabilities are guaranteed to be supported:</p> * <ul> * <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 06397c9a1598..ded96a23e11e 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1484,7 +1484,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>To start a CaptureSession with a target FPS range different from the * capture request template's default value, the application * is strongly recommended to call - * {@link SessionConfiguration#setSessionParameters } + * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters } * with the target fps range before creating the capture session. The aeTargetFpsRange is * typically a session parameter. Specifying it at session creation time helps avoid * session reconfiguration delays in cases like 60fps or high speed recording.</p> @@ -2161,7 +2161,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * OFF if the recording output is not stabilized, or if there are no output * Surface types that can be stabilized.</p> * <p>The application is strongly recommended to call - * {@link SessionConfiguration#setSessionParameters } + * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters } * with the desired video stabilization mode before creating the capture session. * Video stabilization mode is a session parameter on many devices. Specifying * it at session creation time helps avoid reconfiguration delay caused by difference diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index ab4406c37c8e..1d26d69a58b9 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -899,7 +899,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>To start a CaptureSession with a target FPS range different from the * capture request template's default value, the application * is strongly recommended to call - * {@link SessionConfiguration#setSessionParameters } + * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters } * with the target fps range before creating the capture session. The aeTargetFpsRange is * typically a session parameter. Specifying it at session creation time helps avoid * session reconfiguration delays in cases like 60fps or high speed recording.</p> @@ -2382,7 +2382,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * OFF if the recording output is not stabilized, or if there are no output * Surface types that can be stabilized.</p> * <p>The application is strongly recommended to call - * {@link SessionConfiguration#setSessionParameters } + * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters } * with the desired video stabilization mode before creating the capture session. * Video stabilization mode is a session parameter on many devices. Specifying * it at session creation time helps avoid reconfiguration delay caused by difference diff --git a/core/java/android/hardware/camera2/MultiResolutionImageReader.java b/core/java/android/hardware/camera2/MultiResolutionImageReader.java index 0dbf29de5253..8a18a0d2d367 100644 --- a/core/java/android/hardware/camera2/MultiResolutionImageReader.java +++ b/core/java/android/hardware/camera2/MultiResolutionImageReader.java @@ -17,6 +17,7 @@ package android.hardware.camera2; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -33,6 +34,7 @@ import android.os.Message; import android.util.Log; import android.view.Surface; +import com.android.internal.camera.flags.Flags; import java.nio.NioUtils; import java.util.ArrayList; @@ -165,6 +167,104 @@ public class MultiResolutionImageReader implements AutoCloseable { } /** + * <p> + * Create a new multi-resolution reader based on a group of camera stream properties returned + * by a camera device, and the desired format, maximum buffer capacity and consumer usage flag. + * </p> + * <p> + * The valid size and formats depend on the camera characteristics. + * {@code MultiResolutionImageReader} for an image format is supported by the camera device if + * the format is in the supported multi-resolution output stream formats returned by + * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. + * If the image format is supported, the {@code MultiResolutionImageReader} object can be + * created with the {@code streams} objects returned by + * {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputInfo}. + * </p> + * <p> + * The {@code maxImages} parameter determines the maximum number of + * {@link Image} objects that can be acquired from each of the {@code ImageReader} + * within the {@code MultiResolutionImageReader}. However, requesting more buffers will + * use up more memory, so it is important to use only the minimum number necessary. The + * application is strongly recommended to acquire no more than {@code maxImages} images + * from all of the internal ImageReader objects combined. By keeping track of the number of + * acquired images for the MultiResolutionImageReader, the application doesn't need to do the + * bookkeeping for each internal ImageReader returned from {@link + * ImageReader.OnImageAvailableListener#onImageAvailable onImageAvailable} callback. + * </p> + * <p> + * Unlike the normal ImageReader, the MultiResolutionImageReader has a more complex + * configuration sequence. Instead of passing the same surface to OutputConfiguration and + * CaptureRequest, the + * {@link android.hardware.camera2.params.OutputConfiguration#createInstancesForMultiResolutionOutput} + * call needs to be used to create the OutputConfigurations for session creation, and then + * {@link #getSurface} is used to get {@link CaptureRequest.Builder#addTarget the target for + * CaptureRequest}. + * </p> + * @param streams The group of multi-resolution stream info, which is used to create + * a multi-resolution reader containing a number of ImageReader objects. Each + * ImageReader object represents a multi-resolution stream in the group. + * @param format The format of the Image that this multi-resolution reader will produce. + * This must be one of the {@link android.graphics.ImageFormat} or + * {@link android.graphics.PixelFormat} constants. Note that not all formats are + * supported, like ImageFormat.NV21. The supported multi-resolution + * reader format can be queried by {@link + * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getOutputFormats}. + * @param maxImages The maximum number of images the user will want to + * access simultaneously. This should be as small as possible to + * limit memory use. Once maxImages images are obtained by the + * user from any given internal ImageReader, one of them has to be released before + * a new Image will become available for access through the ImageReader's + * {@link ImageReader#acquireLatestImage()} or + * {@link ImageReader#acquireNextImage()}. Must be greater than 0. + * @param usage The intended usage of the images produced by the internal ImageReader. See the usages + * on {@link HardwareBuffer} for a list of valid usage bits. See also + * {@link HardwareBuffer#isSupported(int, int, int, int, long)} for checking + * if a combination is supported. If it's not supported this will throw + * an {@link IllegalArgumentException}. + * @see Image + * @see + * android.hardware.camera2.CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP + * @see + * android.hardware.camera2.params.MultiResolutionStreamConfigurationMap + * + * @hide + */ + @FlaggedApi(Flags.FLAG_MULTIRESOLUTION_IMAGEREADER_USAGE_CONFIG) + public MultiResolutionImageReader( + @NonNull Collection<MultiResolutionStreamInfo> streams, + @Format int format, + @IntRange(from = 1) int maxImages, + @Usage long usage) { + mFormat = format; + mMaxImages = maxImages; + + if (streams == null || streams.size() <= 1) { + throw new IllegalArgumentException( + "The streams info collection must contain at least 2 entries"); + } + if (mMaxImages < 1) { + throw new IllegalArgumentException( + "Maximum outstanding image count must be at least 1"); + } + + if (format == ImageFormat.NV21) { + throw new IllegalArgumentException( + "NV21 format is not supported"); + } + + int numImageReaders = streams.size(); + mReaders = new ImageReader[numImageReaders]; + mStreamInfo = new MultiResolutionStreamInfo[numImageReaders]; + int index = 0; + for (MultiResolutionStreamInfo streamInfo : streams) { + mReaders[index] = ImageReader.newInstance(streamInfo.getWidth(), + streamInfo.getHeight(), format, maxImages, usage); + mStreamInfo[index] = streamInfo; + index++; + } + } + + /** * Set onImageAvailableListener callback. * * <p>This function sets the onImageAvailableListener for all the internal diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 8e49c4c165c4..134a510df5f3 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -403,14 +403,13 @@ public final class DisplayManager { /** * Virtual display flag: Indicates that the display should support system decorations. Virtual - * displays without this flag shouldn't show home, IME or any other system decorations. + * displays without this flag shouldn't show home, navigation bar or wallpaper. * <p>This flag doesn't work without {@link #VIRTUAL_DISPLAY_FLAG_TRUSTED}</p> * * @see #createVirtualDisplay * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED * @hide */ - // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors @TestApi public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9; diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index 7388b5bb1495..9e09759a4282 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -447,7 +447,8 @@ public final class VirtualDisplayConfig implements Parcelable { * Sets whether this display supports showing home activities and wallpaper. * * <p>If set to {@code true}, then the home activity relevant to this display will be - * automatically launched upon the display creation.</p> + * automatically launched upon the display creation. If unset or set to {@code false}, the + * display will not host any activities upon creation.</p> * * <p>Note: setting to {@code true} requires the display to be trusted. If the display is * not trusted, this property is ignored.</p> diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 33960c058baa..145dbf21699e 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -16,6 +16,8 @@ package android.hardware.input; +import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag; + import android.Manifest; import android.annotation.FloatRange; import android.annotation.NonNull; @@ -58,6 +60,11 @@ public class InputSettings { */ public static final float DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH = .8f; + /** + * The maximum allowed Accessibility bounce keys threshold. + * @hide + */ + public static final int MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS = 5000; private InputSettings() { } @@ -328,4 +335,70 @@ public class InputSettings { .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon) || InputProperties.force_enable_stylus_pointer_icon().orElse(false); } + + /** + * Whether Accessibility bounce keys is enabled. + * + * <p> + * ‘Bounce keys’ is an accessibility feature to aid users who have physical disabilities, + * that allows the user to configure the device to ignore rapid, repeated keypresses of the + * same key. + * </p> + * + * @hide + */ + public static boolean isAccessibilityBounceKeysEnabled(@NonNull Context context) { + return getAccessibilityBounceKeysThreshold(context) != 0; + } + + /** + * Get Accessibility bounce keys threshold duration in milliseconds. + * + * <p> + * ‘Bounce keys’ is an accessibility feature to aid users who have physical disabilities, + * that allows the user to configure the device to ignore rapid, repeated keypresses of the + * same key. + * </p> + * + * @hide + */ + public static int getAccessibilityBounceKeysThreshold(@NonNull Context context) { + if (!keyboardA11yBounceKeysFlag()) { + return 0; + } + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS, 0, UserHandle.USER_CURRENT); + } + + /** + * Set Accessibility bounce keys threshold duration in milliseconds. + * @param thresholdTimeMillis time duration for which a key down will be ignored after a + * previous key up for the same key on the same device between 0 and + * {@link MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS} + * + * <p> + * ‘Bounce keys’ is an accessibility feature to aid users who have physical disabilities, + * that allows the user to configure the device to ignore rapid, repeated keypresses of the + * same key. + * </p> + * + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_SETTINGS) + public static void setAccessibilityBounceKeysThreshold(@NonNull Context context, + int thresholdTimeMillis) { + if (!keyboardA11yBounceKeysFlag()) { + return; + } + if (thresholdTimeMillis < 0 + || thresholdTimeMillis > MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS) { + throw new IllegalArgumentException( + "Provided Bounce keys threshold should be in range [0, " + + MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS + "]"); + } + Settings.System.putIntForUser(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS, thresholdTimeMillis, + UserHandle.USER_CURRENT); + } + } diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 97c45a76ade9..362fe78b14b8 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -17,6 +17,12 @@ flag { bug: "294546335" } +flag { + namespace: "input_native" + name: "keyboard_a11y_bounce_keys_flag" + description: "Controls if the bounce keys accessibility feature for physical keyboard is available to the user" + bug: "294546335" +} flag { namespace: "input_native" diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig new file mode 100644 index 000000000000..0ad1804f402c --- /dev/null +++ b/core/java/android/net/flags.aconfig @@ -0,0 +1,50 @@ +package: "com.android.net.flags" + +flag { + name: "track_multiple_network_activities" + namespace: "android_core_networking" + description: "NetworkActivityTracker tracks multiple networks including non default networks" + bug: "267870186" +} + +flag { + name: "forbidden_capability" + namespace: "android_core_networking" + description: "This flag controls the forbidden capability API" + bug: "302997505" +} + +flag { + name: "set_data_saver_via_cm" + namespace: "android_core_networking" + description: "Set data saver through ConnectivityManager API" + bug: "297836825" +} + +flag { + name: "support_is_uid_networking_blocked" + namespace: "android_core_networking" + description: "This flag controls whether isUidNetworkingBlocked is supported" + bug: "297836825" +} + +flag { + name: "basic_background_restrictions_enabled" + namespace: "android_core_networking" + description: "Block network access for apps in a low importance background state" + bug: "304347838" +} + +flag { + name: "register_nsd_offload_engine" + namespace: "android_core_networking" + description: "The flag controls the access for registerOffloadEngine API in NsdManager" + bug: "294777050" +} + +flag { + name: "ipsec_transform_state" + namespace: "android_core_networking_ipsec" + description: "The flag controls the access for getIpSecTransformState and IpSecTransformState" + bug: "308011229" +} diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index 6a83cee10309..05a1abeaf479 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -33,6 +33,7 @@ import java.util.ArrayList; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class BatteryConsumer { private static final String TAG = "BatteryConsumer"; 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/Binder.java b/core/java/android/os/Binder.java index 0ccc485a34f4..02e40cfbecaa 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -396,6 +396,7 @@ public class Binder implements IBinder { * This is Test API which will be used to override output of isDirectlyHandlingTransactionNative * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static void setIsDirectlyHandlingTransactionOverride(boolean isInTransaction) { sIsHandlingBinderTransaction = isInTransaction; } @@ -1068,6 +1069,7 @@ public class Binder implements IBinder { * * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public @Nullable String getTransactionName(int transactionCode) { return null; } @@ -1076,6 +1078,7 @@ public class Binder implements IBinder { * @hide */ @VisibleForTesting + @android.ravenwood.annotation.RavenwoodKeep public final @Nullable String getTransactionTraceName(int transactionCode) { final boolean isInterfaceUserDefined = getMaxTransactionId() == 0; if (mTransactionTraceNames == null) { @@ -1113,6 +1116,7 @@ public class Binder implements IBinder { return transactionTraceName; } + @android.ravenwood.annotation.RavenwoodKeep private @NonNull String getSimpleDescriptor() { String descriptor = mDescriptor; if (descriptor == null) { @@ -1132,6 +1136,7 @@ public class Binder implements IBinder { * @return The highest user-defined transaction id of all transactions. * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public int getMaxTransactionId() { return 0; } diff --git a/core/java/android/os/Broadcaster.java b/core/java/android/os/Broadcaster.java index 88760b0b6828..17cf69296015 100644 --- a/core/java/android/os/Broadcaster.java +++ b/core/java/android/os/Broadcaster.java @@ -19,6 +19,7 @@ package android.os; import android.compat.annotation.UnsupportedAppUsage; /** @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class Broadcaster { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) diff --git a/core/java/android/os/BundleMerger.java b/core/java/android/os/BundleMerger.java index 857aaf57f640..dc243a5e9c08 100644 --- a/core/java/android/os/BundleMerger.java +++ b/core/java/android/os/BundleMerger.java @@ -48,6 +48,7 @@ import java.util.function.BinaryOperator; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BundleMerger implements Parcelable { private static final String TAG = "BundleMerger"; 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/CoolingDevice.java b/core/java/android/os/CoolingDevice.java index 4ddcd9d4ff8b..06ec72051c2f 100644 --- a/core/java/android/os/CoolingDevice.java +++ b/core/java/android/os/CoolingDevice.java @@ -55,7 +55,11 @@ public final class CoolingDevice implements Parcelable { TYPE_TPU, TYPE_POWER_AMPLIFIER, TYPE_DISPLAY, - TYPE_SPEAKER + TYPE_SPEAKER, + TYPE_WIFI, + TYPE_CAMERA, + TYPE_FLASHLIGHT, + TYPE_USB_PORT }) @Retention(RetentionPolicy.SOURCE) public @interface Type {} @@ -84,6 +88,14 @@ public final class CoolingDevice implements Parcelable { public static final int TYPE_DISPLAY = CoolingType.DISPLAY; /** Speaker cooling device */ public static final int TYPE_SPEAKER = CoolingType.SPEAKER; + /** WiFi cooling device */ + public static final int TYPE_WIFI = CoolingType.WIFI; + /** Camera cooling device */ + public static final int TYPE_CAMERA = CoolingType.CAMERA; + /** Flashlight cooling device */ + public static final int TYPE_FLASHLIGHT = CoolingType.FLASHLIGHT; + /** USB PORT cooling device */ + public static final int TYPE_USB_PORT = CoolingType.USB_PORT; /** * Verify a valid cooling device type. @@ -91,7 +103,7 @@ public final class CoolingDevice implements Parcelable { * @return true if a cooling device type is valid otherwise false. */ public static boolean isValidType(@Type int type) { - return type >= TYPE_FAN && type <= TYPE_SPEAKER; + return type >= TYPE_FAN && type <= TYPE_USB_PORT; } public CoolingDevice(long value, @Type int type, @NonNull String name) { diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index c527cb5344c1..04d6f61d84c9 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -2702,4 +2702,13 @@ public final class Debug * @hide */ public static native boolean isVmapStack(); + + /** + * Log internal statistics about the allocator. + * @return true if the statistics were logged properly, false if not. + * + * @hide + */ + public static native boolean logAllocatorStats(); + } diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java index a1d2dcc74246..2aab2b4b18d0 100644 --- a/core/java/android/os/DropBoxManager.java +++ b/core/java/android/os/DropBoxManager.java @@ -81,10 +81,8 @@ public class DropBoxManager { /** * Broadcast Action: This is broadcast when a new entry is added in the dropbox. - * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and later, you - * must hold the {@link android.Manifest.permission#READ_DROPBOX_DATA} permission - * in order to receive this broadcast. For apps targeting Android versions lower - * than {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, you must hold + * For apps targeting 35 and later, For apps targeting Android versions lower + * than 35, you must hold * {@link android.Manifest.permission#READ_LOGS}. * This broadcast can be rate limited for low priority entries * @@ -385,11 +383,8 @@ public class DropBoxManager { /** * Gets the next entry from the drop box <em>after</em> the specified time. * You must always call {@link Entry#close()} on the return value! - * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission is - * required for apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} - * and later. {@link android.Manifest.permission#READ_LOGS} permission is - * required for apps targeting Android versions lower than - * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. + * {@link android.Manifest.permission#READ_LOGS} permission is + * required for apps targeting Android versions lower than 35. * * @param tag of entry to look for, null for all tags * @param msec time of the last entry seen diff --git a/core/java/android/os/PackageTagsList.java b/core/java/android/os/PackageTagsList.java index df99074a851f..cbc427207ccf 100644 --- a/core/java/android/os/PackageTagsList.java +++ b/core/java/android/os/PackageTagsList.java @@ -41,6 +41,7 @@ import java.util.Set; */ @TestApi @Immutable +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class PackageTagsList implements Parcelable { // an empty set value matches any attribution tag (ie, wildcard) 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/Temperature.java b/core/java/android/os/Temperature.java index a138431f745a..2e12278a0ad3 100644 --- a/core/java/android/os/Temperature.java +++ b/core/java/android/os/Temperature.java @@ -79,7 +79,13 @@ public final class Temperature implements Parcelable { TYPE_TPU, TYPE_DISPLAY, TYPE_MODEM, - TYPE_SOC + TYPE_SOC, + TYPE_WIFI, + TYPE_CAMERA, + TYPE_FLASHLIGHT, + TYPE_SPEAKER, + TYPE_AMBIENT, + TYPE_POGO }) @Retention(RetentionPolicy.SOURCE) public @interface Type {} @@ -101,6 +107,12 @@ public final class Temperature implements Parcelable { public static final int TYPE_DISPLAY = TemperatureType.DISPLAY; public static final int TYPE_MODEM = TemperatureType.MODEM; public static final int TYPE_SOC = TemperatureType.SOC; + public static final int TYPE_WIFI = TemperatureType.WIFI; + public static final int TYPE_CAMERA = TemperatureType.CAMERA; + public static final int TYPE_FLASHLIGHT = TemperatureType.FLASHLIGHT; + public static final int TYPE_SPEAKER = TemperatureType.SPEAKER; + public static final int TYPE_AMBIENT = TemperatureType.AMBIENT; + public static final int TYPE_POGO = TemperatureType.POGO; /** * Verify a valid Temperature type. @@ -108,7 +120,7 @@ public final class Temperature implements Parcelable { * @return true if a Temperature type is valid otherwise false. */ public static boolean isValidType(@Type int type) { - return type >= TYPE_UNKNOWN && type <= TYPE_SOC; + return type >= TYPE_UNKNOWN && type <= TYPE_POGO; } /** diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java index 3d8a5504b56d..e514d63aa147 100644 --- a/core/java/android/os/TimestampedValue.java +++ b/core/java/android/os/TimestampedValue.java @@ -35,6 +35,7 @@ import java.util.Objects; * @param <T> the type of the value with an associated timestamp * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class TimestampedValue<T> implements Parcelable { private final long mReferenceTimeMillis; @Nullable diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index bc80c8b20b86..6d4e28403908 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -23,6 +23,7 @@ import java.util.Objects; * Currently the public representation of what a work source is not * defined; this is an opaque container. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class WorkSource implements Parcelable { static final String TAG = "WorkSource"; static final boolean DEBUG = false; @@ -141,11 +142,17 @@ public class WorkSource implements Parcelable { * * @hide */ + @android.ravenwood.annotation.RavenwoodReplace public static boolean isChainedBatteryAttributionEnabled(Context context) { return Settings.Global.getInt(context.getContentResolver(), Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1; } + /** @hide */ + public static boolean isChainedBatteryAttributionEnabled$ravenwood(Context context) { + return false; + } + /** * Returns the number of uids in this work source. * @hide diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 980c13c5c2d6..83d237d6e53b 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -76,3 +76,18 @@ flag { description: "Guards the ADPF GPU APIs." bug: "284324521" } + +flag { + name: "adpf_use_fmq_channel" + namespace: "game" + description: "Guards use of the FMQ channel for ADPF" + bug: "315894228" +} + +flag { + name: "battery_service_support_current_adb_command" + namespace: "backstage_power" + description: "Whether or not BatteryService supports adb commands for Current values." + is_fixed_read_only: true + bug: "315037695" +} diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS index bf22dccce9d4..6941857e8fef 100644 --- a/core/java/android/os/storage/OWNERS +++ b/core/java/android/os/storage/OWNERS @@ -1,6 +1,6 @@ # Bug component: 95221 -# Please assign new bugs to android-storage-triage@, not to individual people +# PLEASE ASSIGN NEW BUGS TO android-storage-triage@, NOT TO INDIVIDUAL PEOPLE # Android Storage Team alukin@google.com 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/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 9fe2599dd39b..5a12760f4e3f 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -318,7 +318,7 @@ public abstract class PermissionControllerService extends Service { * a virtual device. See {@link Context#DEVICE_ID_DEFAULT} */ @BinderThread - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public void onOneTimePermissionSessionTimeout(@NonNull String packageName, int deviceId) { onOneTimePermissionSessionTimeout(packageName); @@ -393,7 +393,7 @@ public abstract class PermissionControllerService extends Service { * @see android.content.Context#revokeSelfPermissionsOnKill(java.util.Collection) */ @BinderThread - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public void onRevokeSelfPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, int deviceId, @NonNull Runnable callback) { onRevokeSelfPermissionsOnKill(packageName, permissions, callback); diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index cbeb82175bfd..d09c2290b38a 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -1,7 +1,7 @@ package: "android.permission.flags" flag { - name: "device_aware_permission_apis" + name: "device_aware_permission_apis_enabled" is_fixed_read_only: true namespace: "permissions" description: "enable device aware permission APIs" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8b5995a740f8..2cc56d838b79 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -608,6 +608,23 @@ public final class Settings { /** * Activity Action: Show settings to allow configuration of + * {@link Manifest.permission#MEDIA_ROUTING_CONTROL} permission. + * + * Input: Optionally, the Intent's data URI can specify the application package name to + * directly invoke the management GUI specific to the package name. For example + * "package:com.my.app". However, modifying this permission setting for any package is allowed + * only when that package holds an appropriate companion device profile such as + * {@link android.companion.AssociationRequest#DEVICE_PROFILE_WATCH}. + * <p> + * Output: Nothing. + */ + @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = + "android.settings.REQUEST_MEDIA_ROUTING_CONTROL"; + + /** + * Activity Action: Show settings to allow configuration of * {@link Manifest.permission#RUN_USER_INITIATED_JOBS} permission * * Input: Optionally, the Intent's data URI can specify the application package name to @@ -7774,6 +7791,16 @@ public final class Settings { public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; /** + * Whether to enable bounce keys for Physical Keyboard accessibility. + * + * If set to non-zero value, any key press on physical keyboard within the provided + * threshold duration (in milliseconds) of the same key, will be ignored. + * + * @hide + */ + public static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys"; + + /** * Whether stylus button presses are disabled. This is a boolean that * determines if stylus buttons are ignored. * @@ -8274,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. * @@ -12109,6 +12147,7 @@ public final class Settings { CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER); CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE); CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD); + CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS); CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES); } 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/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig new file mode 100644 index 000000000000..91a713ee6250 --- /dev/null +++ b/core/java/android/service/dreams/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.service.dreams" + +flag { + name: "dream_overlay_host" + namespace: "communal" + description: "This flag enables using a host to handle displaying a dream's overlay rather than " + "relying on the dream's window" + bug: "291990564" +} diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java index 531626b1e85f..6e771f8f0ffe 100644 --- a/core/java/android/service/notification/Condition.java +++ b/core/java/android/service/notification/Condition.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2014, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +16,8 @@ package android.service.notification; +import static com.android.internal.util.Preconditions.checkArgument; + import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -117,7 +119,7 @@ public final class Condition implements Parcelable { /** The source of, or reason for, the state change represented by this Condition. **/ @FlaggedApi(Flags.FLAG_MODES_API) - public final @Source int source; + public final @Source int source; // default = SOURCE_UNKNOWN /** * The maximum string length for any string contained in this condition. @@ -179,7 +181,7 @@ public final class Condition implements Parcelable { this.line2 = getTrimmedString(line2); this.icon = icon; this.state = state; - this.source = source; + this.source = checkValidSource(source); this.flags = flags; } @@ -197,10 +199,26 @@ public final class Condition implements Parcelable { source.readInt()); } + /** @hide */ + public void validate() { + if (Flags.modesApi()) { + checkValidSource(source); + } + } + private static boolean isValidState(int state) { return state >= STATE_FALSE && state <= STATE_ERROR; } + private static int checkValidSource(@Source int source) { + if (Flags.modesApi()) { + checkArgument(source >= SOURCE_UNKNOWN && source <= SOURCE_CONTEXT, + "Condition source must be one of SOURCE_UNKNOWN, SOURCE_USER_ACTION, " + + "SOURCE_SCHEDULE, or SOURCE_CONTEXT"); + } + return source; + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(id, 0); diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java index 234ff4dd0852..5194cdd47933 100644 --- a/core/java/android/service/notification/DeviceEffectsApplier.java +++ b/core/java/android/service/notification/DeviceEffectsApplier.java @@ -16,6 +16,8 @@ package android.service.notification; +import android.service.notification.ZenModeConfig.ConfigChangeOrigin; + /** * Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that * make sense for the current platform. @@ -33,6 +35,13 @@ public interface DeviceEffectsApplier { * * <p>This will be called whenever the set of consolidated effects changes (normally through * the activation or deactivation of zen rules). + * + * @param effects The effects that should be active and inactive. + * @param source The origin of the change. Because the application of specific effects can be + * disruptive (e.g. lead to Activity recreation), that operation can in some + * cases be deferred (e.g. until screen off). However, if the effects are + * changing as a result of an explicit user action, then it makes sense to + * apply them immediately regardless. */ - void apply(ZenDeviceEffects effects); + void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int source); } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index f6128ea80c3b..fcdc5fe71e4e 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -26,6 +26,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlarmManager; @@ -61,6 +62,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -79,7 +82,66 @@ import java.util.UUID; * @hide */ public class ZenModeConfig implements Parcelable { - private static String TAG = "ZenModeConfig"; + private static final String TAG = "ZenModeConfig"; + + /** + * The {@link ZenModeConfig} is being updated because of an unknown reason. + */ + public static final int UPDATE_ORIGIN_UNKNOWN = 0; + + /** + * The {@link ZenModeConfig} is being updated because of system initialization (i.e. load from + * storage, on device boot). + */ + public static final int UPDATE_ORIGIN_INIT = 1; + + /** The {@link ZenModeConfig} is being updated (replaced) because of a user switch or unlock. */ + public static final int UPDATE_ORIGIN_INIT_USER = 2; + + /** The {@link ZenModeConfig} is being updated because of a user action, for example: + * <ul> + * <li>{@link NotificationManager#setAutomaticZenRuleState} with a + * {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li> + * <li>Adding, updating, or removing a rule from Settings.</li> + * <li>Directly activating or deactivating/snoozing a rule through some UI affordance (e.g. + * Quick Settings).</li> + * </ul> + */ + public static final int UPDATE_ORIGIN_USER = 3; + + /** + * The {@link ZenModeConfig} is being "independently" updated by an app, and not as a result of + * a user's action inside that app (for example, activating an {@link AutomaticZenRule} based on + * a previously set schedule). + */ + public static final int UPDATE_ORIGIN_APP = 4; + + /** + * The {@link ZenModeConfig} is being updated by the System or SystemUI. Note that this only + * includes cases where the call is coming from the System/SystemUI but the change is not due to + * a user action (e.g. automatically activating a schedule-based rule). If the change is a + * result of a user action (e.g. activating a rule by tapping on its QS tile) then + * {@link #UPDATE_ORIGIN_USER} is used instead. + */ + public static final int UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI = 5; + + /** + * The {@link ZenModeConfig} is being updated (replaced) because the user's DND configuration + * is being restored from a backup. + */ + public static final int UPDATE_ORIGIN_RESTORE_BACKUP = 6; + + @IntDef(prefix = { "UPDATE_ORIGIN_" }, value = { + UPDATE_ORIGIN_UNKNOWN, + UPDATE_ORIGIN_INIT, + UPDATE_ORIGIN_INIT_USER, + UPDATE_ORIGIN_USER, + UPDATE_ORIGIN_APP, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + UPDATE_ORIGIN_RESTORE_BACKUP + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConfigChangeOrigin {} public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY; public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS; @@ -1918,6 +1980,7 @@ public class ZenModeConfig implements Parcelable { @Nullable public ZenDeviceEffects zenDeviceEffects; public boolean modified; // rule has been modified from initial creation public String pkg; + @AutomaticZenRule.Type public int type = AutomaticZenRule.TYPE_UNKNOWN; public String triggerDescription; public String iconResName; diff --git a/core/java/android/service/persistentdata/OWNERS b/core/java/android/service/persistentdata/OWNERS new file mode 100644 index 000000000000..6dfb888dedad --- /dev/null +++ b/core/java/android/service/persistentdata/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pdb/OWNERS diff --git a/core/java/android/service/voice/HotwordRejectedResult.java b/core/java/android/service/voice/HotwordRejectedResult.java index 26c1ca428c3b..eb1ac67719ed 100644 --- a/core/java/android/service/voice/HotwordRejectedResult.java +++ b/core/java/android/service/voice/HotwordRejectedResult.java @@ -16,9 +16,11 @@ package android.service.voice; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.SystemApi; import android.os.Parcelable; +import android.service.voice.flags.Flags; import com.android.internal.util.DataClass; @@ -53,12 +55,17 @@ public final class HotwordRejectedResult implements Parcelable { /** High confidence in hotword detector result. */ public static final int CONFIDENCE_LEVEL_HIGH = 3; + /** Very high confidence in hotword detector result. **/ + @FlaggedApi(Flags.FLAG_ALLOW_HOTWORD_BUMP_EGRESS) + public static final int CONFIDENCE_LEVEL_VERY_HIGH = 4; + /** @hide */ @IntDef(prefix = {"CONFIDENCE_LEVEL_"}, value = { CONFIDENCE_LEVEL_NONE, CONFIDENCE_LEVEL_LOW, CONFIDENCE_LEVEL_MEDIUM, - CONFIDENCE_LEVEL_HIGH + CONFIDENCE_LEVEL_HIGH, + CONFIDENCE_LEVEL_VERY_HIGH }) @Retention(RetentionPolicy.SOURCE) @interface HotwordConfidenceLevelValue { @@ -91,9 +98,10 @@ public final class HotwordRejectedResult implements Parcelable { CONFIDENCE_LEVEL_NONE, CONFIDENCE_LEVEL_LOW, CONFIDENCE_LEVEL_MEDIUM, - CONFIDENCE_LEVEL_HIGH + CONFIDENCE_LEVEL_HIGH, + CONFIDENCE_LEVEL_VERY_HIGH }) - @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member public @interface ConfidenceLevel {} @@ -109,6 +117,8 @@ public final class HotwordRejectedResult implements Parcelable { return "CONFIDENCE_LEVEL_MEDIUM"; case CONFIDENCE_LEVEL_HIGH: return "CONFIDENCE_LEVEL_HIGH"; + case CONFIDENCE_LEVEL_VERY_HIGH: + return "CONFIDENCE_LEVEL_VERY_HIGH"; default: return Integer.toHexString(value); } } @@ -259,10 +269,10 @@ public final class HotwordRejectedResult implements Parcelable { } @DataClass.Generated( - time = 1621961370106L, + time = 1701990933632L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/HotwordRejectedResult.java", - inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_HIGH\nprivate final @android.service.voice.HotwordRejectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate static int defaultConfidenceLevel()\nclass HotwordRejectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final @android.annotation.FlaggedApi int CONFIDENCE_LEVEL_VERY_HIGH\nprivate final @android.service.voice.HotwordRejectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate static int defaultConfidenceLevel()\nclass HotwordRejectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java index d184b1eb96ab..76b076be8fab 100644 --- a/core/java/android/service/voice/VisualQueryDetectionService.java +++ b/core/java/android/service/voice/VisualQueryDetectionService.java @@ -338,16 +338,24 @@ public abstract class VisualQueryDetectionService extends Service /** * Overrides {@link Context#openFileInput} to read files with the given file names under the - * internal app storage of the {@link VoiceInteractionService}, i.e., only files stored in - * {@link Context#getFilesDir()} can be opened. + * internal app storage of the {@link VoiceInteractionService}, i.e., the input file path would + * be added with {@link Context#getFilesDir()} as prefix. + * + * @param filename Relative path of a file under {@link Context#getFilesDir()}. + * @throws FileNotFoundException if the file does not exist or cannot be open. */ @Override - public @Nullable FileInputStream openFileInput(@NonNull String filename) throws + public @NonNull FileInputStream openFileInput(@NonNull String filename) throws FileNotFoundException { try { AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>(); + assert mDetectorSessionStorageService != null; mDetectorSessionStorageService.openFile(filename, future); ParcelFileDescriptor pfd = future.get(); + if (pfd == null) { + throw new FileNotFoundException( + "File does not exist. Unable to open " + filename + "."); + } return new FileInputStream(pfd.getFileDescriptor()); } catch (RemoteException | ExecutionException | InterruptedException e) { Log.w(TAG, "Cannot open file due to remote service failure"); diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index 91de894c1d93..b7d97057a08b 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -447,12 +447,12 @@ public class VisualQueryDetector { public void onOpenFile(String filename, AndroidFuture future) throws RemoteException { Slog.v(TAG, "BinderCallback#onOpenFile " + filename); Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> { - Slog.v(TAG, "onOpenFile: " + filename); + Slog.v(TAG, "onOpenFile: " + filename + "under internal app storage."); File f = new File(mContext.getFilesDir(), filename); ParcelFileDescriptor pfd = null; try { - Slog.d(TAG, "opened a file with ParcelFileDescriptor."); pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); + Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor."); } catch (FileNotFoundException e) { Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned."); } finally { diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index fba09233e4f4..75ab48a43da6 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -1042,13 +1042,13 @@ public class VoiceInteractionService extends Service { @SystemApi @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) - public void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed) { - Log.i(TAG, "setIsReceiveSandboxedTrainingDataAllowed to " + allowed); + public void setShouldReceiveSandboxedTrainingData(boolean allowed) { + Log.i(TAG, "setShouldReceiveSandboxedTrainingData to " + allowed); if (mSystemService == null) { throw new IllegalStateException("Not available until onReady() is called"); } try { - mSystemService.setIsReceiveSandboxedTrainingDataAllowed(allowed); + mSystemService.setShouldReceiveSandboxedTrainingData(allowed); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig index c414ef8a6826..b596666bf607 100644 --- a/core/java/android/service/voice/flags/flags.aconfig +++ b/core/java/android/service/voice/flags/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "This flag allows the hotword detection service to egress training data to the default assistant." bug: "296074924" } + +flag { + name: "allow_hotword_bump_egress" + namespace: "machine_learning" + description: "This flag allows hotword detection service to egress reason code for hotword bump." + bug: "290951024" +} diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index fd5517d29d74..f28574ecb3b2 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -27,9 +27,6 @@ import android.window.SurfaceSyncGroup; import com.android.window.flags.Flags; -import java.util.concurrent.Executor; -import java.util.function.Consumer; - /** * Provides an interface to the root-Surface of a View Hierarchy or Window. This * is used in combination with the {@link android.view.SurfaceControl} API to enable @@ -197,42 +194,6 @@ public interface AttachedSurfaceControl { } /** - * Add a trusted presentation listener on the SurfaceControl associated with this window. - * - * @param t Transaction that the trusted presentation listener is added on. This should - * be applied by the caller. - * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify - * when the to invoke the callback. - * @param executor The {@link Executor} where the callback will be invoked on. - * @param listener The {@link Consumer} that will receive the callbacks when entered or - * exited the threshold. - * - * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl, - * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer) - * - * @hide b/287076178 un-hide with API bump - */ - default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull SurfaceControl.TrustedPresentationThresholds thresholds, - @NonNull Executor executor, @NonNull Consumer<Boolean> listener) { - } - - /** - * Remove a trusted presentation listener on the SurfaceControl associated with this window. - * - * @param t Transaction that the trusted presentation listener removed on. This should - * be applied by the caller. - * @param listener The {@link Consumer} that was previously registered with - * addTrustedPresentationCallback that should be removed. - * - * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl) - * @hide b/287076178 un-hide with API bump - */ - default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull Consumer<Boolean> listener) { - } - - /** * Transfer the currently in progress touch gesture from the host to the requested * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the * SurfaceControlViewHost was created with the current host's inputToken. diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 07dd882807af..1908c64ce42d 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -276,15 +276,14 @@ public final class Display { /** * Display flag: Indicates that the display should show system decorations. * <p> - * This flag identifies secondary displays that should show system decorations, such as status - * bar, navigation bar, home activity or IME. + * This flag identifies secondary displays that should show system decorations, such as + * navigation bar, home activity or wallpaper. * </p> * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p> * * @see #getFlags() * @hide */ - // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 6; /** diff --git a/core/java/android/view/ISurfaceControlViewHostParent.aidl b/core/java/android/view/ISurfaceControlViewHostParent.aidl index f42e00148587..559c20ee4825 100644 --- a/core/java/android/view/ISurfaceControlViewHostParent.aidl +++ b/core/java/android/view/ISurfaceControlViewHostParent.aidl @@ -16,6 +16,7 @@ package android.view; +import android.view.KeyEvent; import android.view.WindowManager; /** @@ -24,4 +25,6 @@ import android.view.WindowManager; */ oneway interface ISurfaceControlViewHostParent { void updateParams(in WindowManager.LayoutParams[] childAttrs); + // To forward the back key event from embedded to host app. + void forwardBackKeyToParent(in KeyEvent keyEvent); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 17bbee6d020f..36b74e39072a 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -73,6 +73,8 @@ import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; import android.window.ScreenCapture; import android.window.WindowContextInfo; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; /** * System private interface to the window manager. @@ -1075,4 +1077,10 @@ interface IWindowManager @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MONITOR_INPUT)") void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId); + + void registerTrustedPresentationListener(in IBinder window, in ITrustedPresentationListener listener, + in TrustedPresentationThresholds thresholds, int id); + + + void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index dbacca5def51..9bf43a390d70 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -162,6 +162,8 @@ interface IWindowSession { * @param flags See {@code View#startDragAndDrop} * @param surface Surface containing drag shadow image * @param touchSource See {@code InputDevice#getSource()} + * @param touchDeviceId device ID of last touch event + * @param pointerId pointer ID of last touch event * @param touchX X coordinate of last touch point * @param touchY Y coordinate of last touch point * @param thumbCenterX X coordinate for the position within the shadow image that should be @@ -171,9 +173,9 @@ interface IWindowSession { * @param data Data transferred by drag and drop * @return Token of drag operation which will be passed to cancelDragAndDrop. */ - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) IBinder performDrag(IWindow window, int flags, in SurfaceControl surface, int touchSource, - float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data); + int touchDeviceId, int touchPointerId, float touchX, float touchY, float thumbCenterX, + float thumbCenterY, in ClipData data); /** * Drops the content of the current drag operation for accessibility diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 405653123f79..4840f003da3e 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -447,6 +447,7 @@ public class SurfaceControlViewHost { addWindowToken(attrs); view.setLayoutParams(attrs); mViewRoot.setView(view, attrs, null); + mViewRoot.setBackKeyCallbackForWindowlessWindow(mWm::forwardBackKeyToParent); } /** diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index a44a95a1677f..108de281a411 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -37,6 +37,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; import android.graphics.RenderNode; +import android.hardware.input.InputManager; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -159,6 +160,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private static final boolean DEBUG = false; private static final boolean DEBUG_POSITION = false; + private static final long FORWARD_BACK_KEY_TOLERANCE_MS = 100; + @UnsupportedAppUsage( maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Track {@link SurfaceHolder#addCallback} instead") @@ -326,6 +329,41 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall }); } } + + @Override + public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) { + runOnUiThread(() -> { + if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) { + return; + } + final ViewRootImpl vri = getViewRootImpl(); + if (vri == null) { + return; + } + final InputManager inputManager = mContext.getSystemService(InputManager.class); + if (inputManager == null) { + return; + } + // Check that the event was created recently. + final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime(); + if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) { + Log.e(TAG, "Ignore the input event that exceed the tolerance time, " + + "exceed " + timeDiff + "ms"); + return; + } + if (inputManager.verifyInputEvent(keyEvent) == null) { + Log.e(TAG, "Received invalid input event"); + return; + } + try { + vri.processingBackKey(true); + vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, + true /* processImmediately */); + } finally { + vri.processingBackKey(false); + } + }); + } }; private final boolean mRtDrivenClipping = Flags.clipSurfaceviews(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 75f8eba01fa2..bb5ee0359b6b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -28340,6 +28340,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, IBinder token = mAttachInfo.mSession.performDrag( mAttachInfo.mWindow, flags, null, mAttachInfo.mViewRootImpl.getLastTouchSource(), + mAttachInfo.mViewRootImpl.getLastTouchDeviceId(), + mAttachInfo.mViewRootImpl.getLastTouchPointerId(), 0f, 0f, 0f, 0f, data); if (ViewDebug.DEBUG_DRAG) { Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token); @@ -28414,7 +28416,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl, - root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y, + root.getLastTouchSource(), root.getLastTouchDeviceId(), + root.getLastTouchPointerId(), lastTouchPoint.x, lastTouchPoint.y, shadowTouchPoint.x, shadowTouchPoint.y, data); if (ViewDebug.DEBUG_DRAG) { Log.d(VIEW_LOG_TAG, "performDrag returned " + token); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 71345290dd50..e83488e2689e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -76,13 +76,8 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_E import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; -import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; @@ -96,6 +91,7 @@ import static android.view.accessibility.Flags.reduceWindowContentChangedEventTh import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; +import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static com.android.input.flags.Flags.enablePointerChoreographer; @@ -255,7 +251,7 @@ import java.util.OptionalInt; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.function.Consumer; +import java.util.function.Predicate; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -375,6 +371,8 @@ public final class ViewRootImpl implements ViewParent, */ private static final int KEEP_CLEAR_AREA_REPORT_RATE_MILLIS = 100; + private static final long NANOS_PER_SEC = 1000000000; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); @@ -620,6 +618,13 @@ public final class ViewRootImpl implements ViewParent, boolean mUpcomingWindowFocus; @GuardedBy("this") boolean mUpcomingInTouchMode; + // While set, allow this VRI to handle back key without drop it. + private boolean mProcessingBackKey; + /** + * Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back + * key event host app. + */ + private Predicate<KeyEvent> mWindowlessBackKeyCallback; public boolean mTraversalScheduled; int mTraversalBarrier; @@ -808,6 +813,8 @@ public final class ViewRootImpl implements ViewParent, final PointF mDragPoint = new PointF(); final PointF mLastTouchPoint = new PointF(); int mLastTouchSource; + int mLastTouchDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD; + int mLastTouchPointerId; /** Tracks last {@link MotionEvent#getToolType(int)} with {@link MotionEvent#ACTION_UP}. **/ private int mLastClickToolType; @@ -822,15 +829,25 @@ public final class ViewRootImpl implements ViewParent, private boolean mInsetsAnimationRunning; + private long mPreviousFrameDrawnTime = -1; + /** * The resolved pointer icon type requested by this window. * A null value indicates the resolved pointer icon has not yet been calculated. */ + // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete. @Nullable private Integer mPointerIconType = null; private PointerIcon mCustomPointerIcon = null; /** + * The resolved pointer icon requested by this window. + * A null value indicates the resolved pointer icon has not yet been calculated. + */ + @Nullable + private PointerIcon mResolvedPointerIcon = null; + + /** * see {@link #playSoundEffect(int)} */ AudioManager mAudioManager; @@ -1051,11 +1068,14 @@ public final class ViewRootImpl implements ViewParent, private boolean mChildBoundingInsetsChanged = false; private String mTag = TAG; + private String mFpsTraceName; private static boolean sToolkitSetFrameRateReadOnlyFlagValue; + private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; static { sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); + sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision(); } // The latest input event from the gesture that was used to resolve the pointer icon. @@ -1299,6 +1319,7 @@ public final class ViewRootImpl implements ViewParent, attrs = mWindowAttributes; setTag(); + mFpsTraceName = "FPS of " + getTitle(); if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0 @@ -3186,7 +3207,11 @@ public final class ViewRootImpl implements ViewParent, host.dispatchAttachedToWindow(mAttachInfo, 0); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); - if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) { + if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled() + // Don't register compat OnBackInvokedCallback for windowless window. + // The onBackInvoked event by default should forward to host app, so the + // host app can decide the behavior. + && mWindowlessBackKeyCallback == null) { // For apps requesting legacy back behavior, we add a compat callback that // dispatches {@link KeyEvent#KEYCODE_BACK} to their root views. // This way from system point of view, these apps are providing custom @@ -4718,6 +4743,31 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * Called from draw() to collect metrics for frame rate decision. + */ + private void collectFrameRateDecisionMetrics() { + if (!Trace.isEnabled()) { + if (mPreviousFrameDrawnTime > 0) mPreviousFrameDrawnTime = -1; + return; + } + + if (mPreviousFrameDrawnTime < 0) { + mPreviousFrameDrawnTime = mChoreographer.getExpectedPresentationTimeNanos(); + return; + } + + long expectedDrawnTime = mChoreographer.getExpectedPresentationTimeNanos(); + long timeDiff = expectedDrawnTime - mPreviousFrameDrawnTime; + if (timeDiff <= 0) { + return; + } + + long fps = NANOS_PER_SEC / timeDiff; + Trace.setCounter(mFpsTraceName, fps); + mPreviousFrameDrawnTime = expectedDrawnTime; + } + private void reportDrawFinished(@Nullable Transaction t, int seqId) { if (DEBUG_BLAST) { Log.d(mTag, "reportDrawFinished"); @@ -5036,6 +5086,9 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_FPS) { trackFPS(); } + if (sToolkitMetricsForFrameRateDecisionFlagValue) { + collectFrameRateDecisionMetrics(); + } if (!sFirstDrawComplete) { synchronized (sFirstDrawHandlers) { @@ -6652,7 +6705,8 @@ public final class ViewRootImpl implements ViewParent, // Find a reason for dropping or canceling the event. final String reason; - if (!mAttachInfo.mHasWindowFocus + // The embedded window is focused, allow this VRI to handle back key. + if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent)) && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && !isAutofillUiShowing()) { // This is a non-pointer event and the window doesn't currently have input focus @@ -6875,10 +6929,20 @@ public final class ViewRootImpl implements ViewParent, // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}. - if (isBack(keyEvent) - && mContext != null - && mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) { - return doOnBackKeyEvent(keyEvent); + if (isBack(keyEvent)) { + if (mWindowlessBackKeyCallback != null) { + if (mWindowlessBackKeyCallback.test(keyEvent)) { + return keyEvent.getAction() == KeyEvent.ACTION_UP + && !keyEvent.isCanceled() + ? FINISH_HANDLED : FINISH_NOT_HANDLED; + } else { + // Unable to forward the back key to host, forward to next stage. + return FORWARD; + } + } else if (mContext != null + && mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) { + return doOnBackKeyEvent(keyEvent); + } } if (mInputQueue != null) { @@ -7101,6 +7165,8 @@ public final class ViewRootImpl implements ViewParent, mLastTouchPoint.x = event.getRawX(); mLastTouchPoint.y = event.getRawY(); mLastTouchSource = event.getSource(); + mLastTouchDeviceId = event.getDeviceId(); + mLastTouchPointerId = event.getPointerId(0); // Register last ACTION_UP. This will be propagated to IME. if (event.getActionMasked() == MotionEvent.ACTION_UP) { @@ -7410,12 +7476,14 @@ public final class ViewRootImpl implements ViewParent, // Other apps or the window manager may change the icon type outside of // this app, therefore the icon type has to be reset on enter/exit event. mPointerIconType = null; + mResolvedPointerIcon = null; } if (action != MotionEvent.ACTION_HOVER_EXIT) { // Resolve the pointer icon if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) { mPointerIconType = null; + mResolvedPointerIcon = null; } } @@ -7470,6 +7538,7 @@ public final class ViewRootImpl implements ViewParent, private void resetPointerIcon(MotionEvent event) { mPointerIconType = null; + mResolvedPointerIcon = null; updatePointerIcon(event); } @@ -7507,6 +7576,21 @@ public final class ViewRootImpl implements ViewParent, pointerIcon = mView.onResolvePointerIcon(event, pointerIndex); } + if (enablePointerChoreographer()) { + if (pointerIcon == null) { + pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED); + } + if (Objects.equals(mResolvedPointerIcon, pointerIcon)) { + return true; + } + mResolvedPointerIcon = pointerIcon; + + InputManagerGlobal.getInstance() + .setPointerIcon(pointerIcon, event.getDisplayId(), + event.getDeviceId(), event.getPointerId(0), getInputToken()); + return true; + } + final int pointerType = (pointerIcon != null) ? pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED; @@ -7514,34 +7598,18 @@ public final class ViewRootImpl implements ViewParent, mPointerIconType = pointerType; mCustomPointerIcon = null; if (mPointerIconType != PointerIcon.TYPE_CUSTOM) { - if (enablePointerChoreographer()) { - InputManagerGlobal - .getInstance() - .setPointerIcon(PointerIcon.getSystemIcon(mContext, pointerType), - event.getDisplayId(), event.getDeviceId(), - event.getPointerId(pointerIndex), getInputToken()); - } else { - InputManagerGlobal - .getInstance() - .setPointerIconType(pointerType); - } + InputManagerGlobal + .getInstance() + .setPointerIconType(pointerType); return true; } } if (mPointerIconType == PointerIcon.TYPE_CUSTOM && !pointerIcon.equals(mCustomPointerIcon)) { mCustomPointerIcon = pointerIcon; - if (enablePointerChoreographer()) { - InputManagerGlobal - .getInstance() - .setPointerIcon(mCustomPointerIcon, - event.getDisplayId(), event.getDeviceId(), - event.getPointerId(pointerIndex), getInputToken()); - } else { - InputManagerGlobal - .getInstance() - .setCustomPointerIcon(mCustomPointerIcon); - } + InputManagerGlobal + .getInstance() + .setCustomPointerIcon(mCustomPointerIcon); } return true; } @@ -8510,6 +8578,14 @@ public final class ViewRootImpl implements ViewParent, return mLastTouchSource; } + public int getLastTouchDeviceId() { + return mLastTouchDeviceId; + } + + public int getLastTouchPointerId() { + return mLastTouchPointerId; + } + /** * Used by InputMethodManager. * @hide @@ -10519,6 +10595,11 @@ public final class ViewRootImpl implements ViewParent, mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget(); } + // Make this VRI able to process back key without drop it. + void processingBackKey(boolean processing) { + mProcessingBackKey = processing; + } + /** * Collect and include any ScrollCaptureCallback instances registered with the window. * @@ -11743,13 +11824,18 @@ public final class ViewRootImpl implements ViewParent, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); - enqueueInputEvent(ev); + enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */); } private void registerCompatOnBackInvokedCallback() { mCompatOnBackInvokedCallback = () -> { - sendBackKeyEvent(KeyEvent.ACTION_DOWN); - sendBackKeyEvent(KeyEvent.ACTION_UP); + try { + processingBackKey(true); + sendBackKeyEvent(KeyEvent.ACTION_DOWN); + sendBackKeyEvent(KeyEvent.ACTION_UP); + } finally { + processingBackKey(false); + } }; if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) { Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher"); @@ -12016,18 +12102,6 @@ public final class ViewRootImpl implements ViewParent, scheduleTraversals(); } - @Override - public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull SurfaceControl.TrustedPresentationThresholds thresholds, - @NonNull Executor executor, @NonNull Consumer<Boolean> listener) { - t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener); - } - - @Override - public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull Consumer<Boolean> listener) { - t.clearTrustedPresentationCallback(getSurfaceControl()); - } private void logAndTrace(String msg) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { @@ -12099,11 +12173,9 @@ public final class ViewRootImpl implements ViewParent, boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN || motionEventAction == MotionEvent.ACTION_MOVE || motionEventAction == MotionEvent.ACTION_UP; - boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION - || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION - || windowType == TYPE_NOTIFICATION_SHADE || windowType == TYPE_STATUS_BAR; + boolean undesiredType = windowType == TYPE_INPUT_METHOD; // use toolkitSetFrameRate flag to gate the change - return desiredAction && desiredType && sToolkitSetFrameRateReadOnlyFlagValue; + return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue; } /** @@ -12198,4 +12270,13 @@ public final class ViewRootImpl implements ViewParent, } return false; } + + /** + * Set the default back key callback for windowless window, to forward the back key event + * to host app. + * MUST NOT call this method for normal window. + */ + void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) { + mWindowlessBackKeyCallback = callback; + } } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index c9526fde27df..3b444c44c368 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -75,7 +75,7 @@ public final class ViewTreeObserver { private boolean mWindowShown; // The reason that the last call to dispatchOnPreDraw() returned true to cancel and redraw - private String mLastDispatchOnPreDrawCanceledReason; + private StringBuilder mLastDispatchOnPreDrawCanceledReason; private boolean mAlive = true; @@ -1173,9 +1173,15 @@ public final class ViewTreeObserver { int count = access.size(); for (int i = 0; i < count; i++) { final OnPreDrawListener preDrawListener = access.get(i); - cancelDraw |= !(preDrawListener.onPreDraw()); - if (cancelDraw) { - mLastDispatchOnPreDrawCanceledReason = preDrawListener.getClass().getName(); + final boolean listenerCanceledDraw = !(preDrawListener.onPreDraw()); + cancelDraw |= listenerCanceledDraw; + if (listenerCanceledDraw) { + final String className = preDrawListener.getClass().getName(); + if (mLastDispatchOnPreDrawCanceledReason == null) { + mLastDispatchOnPreDrawCanceledReason = new StringBuilder(className); + } else { + mLastDispatchOnPreDrawCanceledReason.append("|").append(className); + } } } } finally { @@ -1191,7 +1197,10 @@ public final class ViewTreeObserver { * @hide */ final String getLastDispatchOnPreDrawCanceledReason() { - return mLastDispatchOnPreDrawCanceledReason; + if (mLastDispatchOnPreDrawCanceledReason != null) { + return mLastDispatchOnPreDrawCanceledReason.toString(); + } + return null; } /** diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 046ea77f196d..c7e180732fd4 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -122,7 +122,11 @@ import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.accessibility.AccessibilityNodeInfo; +import android.window.ITrustedPresentationListener; import android.window.TaskFpsCallback; +import android.window.TrustedPresentationThresholds; + +import com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -656,26 +660,35 @@ public interface WindowManager extends ViewManager { /** * Display IME Policy: The IME should appear on the local display. + * * @hide */ - @TestApi + @SuppressLint("UnflaggedApi") // promoting from @TestApi. + @SystemApi int DISPLAY_IME_POLICY_LOCAL = 0; /** - * Display IME Policy: The IME should appear on the fallback display. + * Display IME Policy: The IME should appear on a fallback display. + * + * <p>The fallback display is always {@link Display#DEFAULT_DISPLAY}.</p> + * * @hide */ - @TestApi + @SuppressLint("UnflaggedApi") // promoting from @TestApi. + @SystemApi int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; /** * Display IME Policy: The IME should be hidden. * - * Setting this policy will prevent the IME from making a connection. This - * will prevent any IME from receiving metadata about input. + * <p>Setting this policy will prevent the IME from making a connection. This + * will prevent any IME from receiving metadata about input and this display will effectively + * have no IME.</p> + * * @hide */ - @TestApi + @SuppressLint("UnflaggedApi") // promoting from @TestApi. + @SystemApi int DISPLAY_IME_POLICY_HIDE = 2; /** @@ -3251,6 +3264,13 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24; /** + * Flag to indicate that the window consumes the insets of {@link Type#ime()}. This makes + * windows below this window unable to receive visible IME insets. + * @hide + */ + public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25; + + /** * Flag to indicate that the window is controlling the appearance of system bars. So we * don't need to adjust it by reading its system UI flags for compatibility. * @hide @@ -3334,6 +3354,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, PRIVATE_FLAG_NOT_MAGNIFIABLE, PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, + PRIVATE_FLAG_CONSUME_IME_INSETS, PRIVATE_FLAG_APPEARANCE_CONTROLLED, PRIVATE_FLAG_BEHAVIOR_CONTROLLED, PRIVATE_FLAG_FIT_INSETS_CONTROLLED, @@ -3432,6 +3453,10 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, name = "COLOR_SPACE_AGNOSTIC"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_CONSUME_IME_INSETS, + equals = PRIVATE_FLAG_CONSUME_IME_INSETS, + name = "CONSUME_IME_INSETS"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED, equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED, name = "APPEARANCE_CONTROLLED"), @@ -3458,7 +3483,7 @@ public interface WindowManager extends ViewManager { @ViewDebug.FlagToString( mask = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, equals = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, - name = "PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY") + name = "SYSTEM_APPLICATION_OVERLAY") }) @PrivateFlags @TestApi @@ -5884,4 +5909,34 @@ public interface WindowManager extends ViewManager { default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) { throw new UnsupportedOperationException(); } + + /** + * Add a trusted presentation listener associated with a window. + * + * <p> If this listener is already registered then the window and thresholds will be updated. + * + * @param window The Window to add the trusted presentation listener for + * @param thresholds The {@link TrustedPresentationThresholds} that will specify + * when the to invoke the callback. + * @param executor The {@link Executor} where the callback will be invoked on. + * @param listener The {@link Consumer} that will receive the callbacks + * when entered or exited trusted presentation per the thresholds. + */ + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + default void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor, + @NonNull Consumer<Boolean> listener) { + throw new UnsupportedOperationException(); + } + + /** + * Removes a presentation listener associated with a window. If the listener was not previously + * registered, the call will be a noop. + * + * @see WindowManager#registerTrustedPresentationListener(IBinder, TrustedPresentationThresholds, Executor, Consumer) + */ + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + throw new UnsupportedOperationException(); + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 214f1ec3d1ec..f1e406196abf 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -30,9 +30,13 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.util.AndroidRuntimeException; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.view.inputmethod.InputMethodManager; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; import com.android.internal.util.FastPrintWriter; @@ -43,6 +47,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.WeakHashMap; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -143,6 +148,9 @@ public final class WindowManagerGlobal { private Runnable mSystemPropertyUpdater; + private final TrustedPresentationListener mTrustedPresentationListener = + new TrustedPresentationListener(); + private WindowManagerGlobal() { } @@ -324,7 +332,7 @@ public final class WindowManagerGlobal { final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags - & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { + & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } @@ -482,7 +490,7 @@ public final class WindowManagerGlobal { if (who != null) { WindowLeaked leak = new WindowLeaked( what + " " + who + " has leaked window " - + root.getView() + " that was originally added here"); + + root.getView() + " that was originally added here"); leak.setStackTrace(root.getLocation().getStackTrace()); Log.e(TAG, "", leak); } @@ -790,6 +798,87 @@ public final class WindowManagerGlobal { } } + public void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, Executor executor, + @NonNull Consumer<Boolean> listener) { + mTrustedPresentationListener.addListener(window, thresholds, listener, executor); + } + + public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + mTrustedPresentationListener.removeListener(listener); + } + + private final class TrustedPresentationListener extends + ITrustedPresentationListener.Stub { + private static int sId = 0; + private final ArrayMap<Consumer<Boolean>, Pair<Integer, Executor>> mListeners = + new ArrayMap<>(); + + private final Object mTplLock = new Object(); + + private void addListener(IBinder window, TrustedPresentationThresholds thresholds, + Consumer<Boolean> listener, Executor executor) { + synchronized (mTplLock) { + if (mListeners.containsKey(listener)) { + Log.i(TAG, "Updating listener " + listener + " thresholds to " + thresholds); + removeListener(listener); + } + int id = sId++; + mListeners.put(listener, new Pair<>(id, executor)); + try { + WindowManagerGlobal.getWindowManagerService() + .registerTrustedPresentationListener(window, this, thresholds, id); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + } + + private void removeListener(Consumer<Boolean> listener) { + synchronized (mTplLock) { + var removedListener = mListeners.remove(listener); + if (removedListener == null) { + Log.i(TAG, "listener " + listener + " does not exist."); + return; + } + + try { + WindowManagerGlobal.getWindowManagerService() + .unregisterTrustedPresentationListener(this, removedListener.first); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + } + + @Override + public void onTrustedPresentationChanged(int[] inTrustedStateListenerIds, + int[] outOfTrustedStateListenerIds) { + ArrayList<Runnable> firedListeners = new ArrayList<>(); + synchronized (mTplLock) { + mListeners.forEach((listener, idExecutorPair) -> { + final var listenerId = idExecutorPair.first; + final var executor = idExecutorPair.second; + for (int id : inTrustedStateListenerIds) { + if (listenerId == id) { + firedListeners.add(() -> executor.execute( + () -> listener.accept(/*presentationState*/true))); + } + } + for (int id : outOfTrustedStateListenerIds) { + if (listenerId == id) { + firedListeners.add(() -> executor.execute( + () -> listener.accept(/*presentationState*/false))); + } + } + }); + } + for (int i = 0; i < firedListeners.size(); i++) { + firedListeners.get(i).run(); + } + } + } + /** @hide */ public void addWindowlessRoot(ViewRootImpl impl) { synchronized (mLock) { @@ -801,7 +890,7 @@ public final class WindowManagerGlobal { public void removeWindowlessRoot(ViewRootImpl impl) { synchronized (mLock) { mWindowlessRoots.remove(impl); - } + } } public void setRecentsAppBehindSystemBars(boolean behindSystemBars) { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index d7b74b3bcfe2..b4b1fde89a46 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -37,6 +37,7 @@ import android.os.StrictMode; import android.util.Log; import android.window.ITaskFpsCallback; import android.window.TaskFpsCallback; +import android.window.TrustedPresentationThresholds; import android.window.WindowContext; import android.window.WindowMetricsController; import android.window.WindowProvider; @@ -508,4 +509,17 @@ public final class WindowManagerImpl implements WindowManager { } return false; } + + @Override + public void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor, + @NonNull Consumer<Boolean> listener) { + mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener); + } + + @Override + public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + mGlobal.unregisterTrustedPresentationListener(listener); + + } } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index d817e6f51f55..d6ac56239aed 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.res.Configuration; @@ -488,8 +489,9 @@ public class WindowlessWindowManager implements IWindowSession { @Override public android.os.IBinder performDrag(android.view.IWindow window, int flags, - android.view.SurfaceControl surface, int touchSource, float touchX, float touchY, - float thumbCenterX, float thumbCenterY, android.content.ClipData data) { + android.view.SurfaceControl surface, int touchSource, int touchDeviceId, + int touchPointerId, float touchX, float touchY, float thumbCenterX, float thumbCenterY, + android.content.ClipData data) { return null; } @@ -703,4 +705,17 @@ public class WindowlessWindowManager implements IWindowSession { } } } + + boolean forwardBackKeyToParent(@NonNull KeyEvent keyEvent) { + if (mParentInterface == null) { + return false; + } + try { + mParentInterface.forwardBackKeyToParent(keyEvent); + } catch (RemoteException e) { + Log.e(TAG, "Failed to forward back key To Parent: ", e); + return false; + } + return true; + } } diff --git a/core/java/android/view/accessibility/IMagnificationConnection.aidl b/core/java/android/view/accessibility/IMagnificationConnection.aidl index a5e8aaf97de4..aae51abd3c78 100644 --- a/core/java/android/view/accessibility/IMagnificationConnection.aidl +++ b/core/java/android/view/accessibility/IMagnificationConnection.aidl @@ -18,7 +18,7 @@ package android.view.accessibility; import android.graphics.PointF; import android.graphics.Rect; -import android.view.accessibility.IWindowMagnificationConnectionCallback; +import android.view.accessibility.IMagnificationConnectionCallback; import android.view.accessibility.IRemoteMagnificationAnimationCallback; /** @@ -110,11 +110,11 @@ oneway interface IMagnificationConnection { void removeMagnificationSettingsPanel(int displayId); /** - * Sets {@link IWindowMagnificationConnectionCallback} to receive the request or the callback. + * Sets {@link IMagnificationConnectionCallback} to receive the request or the callback. * * @param callback the interface to be called. */ - void setConnectionCallback(in IWindowMagnificationConnectionCallback callback); + void setConnectionCallback(in IMagnificationConnectionCallback callback); /** * Notify System UI the magnification scale on the specified display for userId is changed. diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IMagnificationConnectionCallback.aidl index 21b433465a3a..0ba61b10811f 100644 --- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IMagnificationConnectionCallback.aidl @@ -24,7 +24,7 @@ import android.graphics.Rect; * * @hide */ - oneway interface IWindowMagnificationConnectionCallback { + oneway interface IMagnificationConnectionCallback { /** * Called when the bounds of the mirrow window is changed. 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/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig index f3dc33cd2cc9..2a3008a53635 100644 --- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig +++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig @@ -27,3 +27,10 @@ flag { description: "If true, an appop is logged on creation of accessibility overlays." bug: "289081465" } + +flag { + name: "rapid_clear_notifications_by_listener_app_op_enabled" + namespace: "content_protection" + description: "If true, an appop is logged when a notification is rapidly cleared by a notification listener." + bug: "289080543" +} diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index a467afe5d06a..0aa516e08697 100644 --- a/core/java/android/view/flags/refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -42,4 +42,12 @@ flag { namespace: "core_graphics" description: "Enable the `setFrameRate` callback" bug: "299946220" +} + +flag { + name: "toolkit_metrics_for_frame_rate_decision" + namespace: "toolkit" + description: "Feature flag for toolkit metrics collecting for frame rate decision" + bug: "301343249" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index fab8c7796dfd..1b7d57b785f5 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -20,8 +20,8 @@ import static android.view.InsetsController.ANIMATION_TYPE_HIDE; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION; +import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_HIDE_ANIMATION; +import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_SHOW_ANIMATION; import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN; import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN; @@ -758,7 +758,7 @@ public interface ImeTracker { * A helper method to translate animation type to CUJ type for IME animations. * * @param animType the animation type. - * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType}, + * @return the integer in {@link com.android.internal.jank.Cuj.CujType}, * or {@code -1} if the animation type is not supported for tracking yet. */ private static int getImeInsetsCujFromAnimation(@AnimationType int animType) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index da3134856731..8ad10af7250a 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -16,6 +16,7 @@ package android.widget; +import static android.appwidget.flags.Flags.remoteAdapterConversion; import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; import android.annotation.AttrRes; @@ -36,7 +37,6 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; import android.app.ActivityThread; -import android.app.AppGlobals; import android.app.Application; import android.app.LoadedApk; import android.app.PendingIntent; @@ -108,7 +108,6 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.CompoundButton.OnCheckedChangeListener; import com.android.internal.R; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.util.Preconditions; import com.android.internal.widget.IRemoteViewsFactory; @@ -4950,21 +4949,11 @@ public class RemoteViews implements Parcelable, Filter { */ @Deprecated public void setRemoteAdapter(@IdRes int viewId, Intent intent) { - if (isAdapterConversionEnabled()) { + if (remoteAdapterConversion()) { addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent)); - return; + } else { + addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); } - addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); - } - - /** - * @hide - * @return True if the remote adapter conversion is enabled - */ - public static boolean isAdapterConversionEnabled() { - return AppGlobals.getIntCoreSetting( - SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION, - SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0; } /** diff --git a/core/java/android/window/ITrustedPresentationListener.aidl b/core/java/android/window/ITrustedPresentationListener.aidl new file mode 100644 index 000000000000..b33128abb7e5 --- /dev/null +++ b/core/java/android/window/ITrustedPresentationListener.aidl @@ -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 android.window; + +/** + * @hide + */ +oneway interface ITrustedPresentationListener { + void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds); +}
\ No newline at end of file diff --git a/core/java/android/window/TrustedPresentationListener.java b/core/java/android/window/TrustedPresentationListener.java new file mode 100644 index 000000000000..02fd6d98fb0d --- /dev/null +++ b/core/java/android/window/TrustedPresentationListener.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * @hide + */ +public interface TrustedPresentationListener { + + void onTrustedPresentationChanged(boolean inTrustedPresentationState); + +} diff --git a/core/java/android/window/TrustedPresentationThresholds.aidl b/core/java/android/window/TrustedPresentationThresholds.aidl new file mode 100644 index 000000000000..d7088bf0fddc --- /dev/null +++ b/core/java/android/window/TrustedPresentationThresholds.aidl @@ -0,0 +1,3 @@ +package android.window; + +parcelable TrustedPresentationThresholds; diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java new file mode 100644 index 000000000000..90f8834b37d1 --- /dev/null +++ b/core/java/android/window/TrustedPresentationThresholds.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.SuppressLint; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; + +import com.android.window.flags.Flags; + +/** + * Threshold values that are sent with + * {@link android.view.WindowManager#registerTrustedPresentationListener(IBinder, + * TrustedPresentationThresholds, Executor, Consumer)} + */ +@FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) +public final class TrustedPresentationThresholds implements Parcelable { + /** + * The min alpha the {@link SurfaceControl} is required to have to be considered inside the + * threshold. + */ + @FloatRange(from = 0f, fromInclusive = false, to = 1f) + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + @SuppressLint("InternalField") // simple data class + public final float minAlpha; + + /** + * The min fraction of the SurfaceControl that was presented to the user to be considered + * inside the threshold. + */ + @FloatRange(from = 0f, fromInclusive = false, to = 1f) + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + @SuppressLint("InternalField") // simple data class + public final float minFractionRendered; + + /** + * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold. + */ + @IntRange(from = 1) + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + @SuppressLint("InternalField") // simple data class + public final int stabilityRequirementMs; + + private void checkValid() { + if (minAlpha <= 0 || minFractionRendered <= 0 || stabilityRequirementMs < 1) { + throw new IllegalArgumentException( + "TrustedPresentationThresholds values are invalid"); + } + } + + /** + * Creates a new TrustedPresentationThresholds. + * + * @param minAlpha The min alpha the {@link SurfaceControl} is required to + * have to be considered inside the + * threshold. + * @param minFractionRendered The min fraction of the SurfaceControl that was presented + * to the user to be considered + * inside the threshold. + * @param stabilityRequirementMs The time in milliseconds required for the + * {@link SurfaceControl} to be in the threshold. + */ + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public TrustedPresentationThresholds( + @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha, + @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered, + @IntRange(from = 1) int stabilityRequirementMs) { + this.minAlpha = minAlpha; + this.minFractionRendered = minFractionRendered; + this.stabilityRequirementMs = stabilityRequirementMs; + checkValid(); + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public String toString() { + return "TrustedPresentationThresholds { " + + "minAlpha = " + minAlpha + ", " + + "minFractionRendered = " + minFractionRendered + ", " + + "stabilityRequirementMs = " + stabilityRequirementMs + + " }"; + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeFloat(minAlpha); + dest.writeFloat(minFractionRendered); + dest.writeInt(stabilityRequirementMs); + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public int describeContents() { + return 0; + } + + /** + * @hide + */ + TrustedPresentationThresholds(@NonNull Parcel in) { + minAlpha = in.readFloat(); + minFractionRendered = in.readFloat(); + stabilityRequirementMs = in.readInt(); + + checkValid(); + } + + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public static final @NonNull Creator<TrustedPresentationThresholds> CREATOR = + new Creator<TrustedPresentationThresholds>() { + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public TrustedPresentationThresholds[] newArray(int size) { + return new TrustedPresentationThresholds[size]; + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public TrustedPresentationThresholds createFromParcel(@NonNull Parcel in) { + return new TrustedPresentationThresholds(in); + } + }; +} diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 29932f342b74..56df49370379 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -56,3 +56,11 @@ flag { is_fixed_read_only: true bug: "308662081" } + +flag { + namespace: "window_surfaces" + name: "trusted_presentation_listener_for_window" + description: "Enable trustedPresentationListener on windows public API" + is_fixed_read_only: true + bug: "278027319" +}
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 31a3ebd9b32e..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 @@ -44,11 +44,18 @@ flag { bug: "294925498" } - flag { name: "wallpaper_offset_async" namespace: "windowing_frontend" description: "Do not synchronise the wallpaper offset" bug: "293248754" is_fixed_read_only: true +} + +flag { + name: "predictive_back_system_animations" + namespace: "systemui" + description: "Predictive back for system animations" + bug: "309545085" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 82ee8fc47571..e92c6a6c4b34 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -397,5 +397,5 @@ interface IVoiceInteractionManagerService { * sandboxed detection (from trusted process). */ @EnforcePermission("MANAGE_HOTWORD_DETECTION") - void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed); + void setShouldReceiveSandboxedTrainingData(boolean allowed); } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index e494346bae5c..bd806bfb3e24 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -519,24 +519,6 @@ public final class SystemUiDeviceConfigFlags { public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot"; /** - * (boolean) Whether to enable the adapter conversion in RemoteViews - */ - public static final String REMOTEVIEWS_ADAPTER_CONVERSION = - "CursorControlFeature__remoteviews_adapter_conversion"; - - /** - * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION} - */ - public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION = - "systemui__remoteviews_adapter_conversion"; - - /** - * Default value for whether the adapter conversion is enabled or not. This is set for - * RemoteViews and should not be a common practice. - */ - public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false; - - /** * (boolean) Whether the task manager should show a stop button if the app is allowlisted * by the user. */ diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java new file mode 100644 index 000000000000..96740c59ec06 --- /dev/null +++ b/core/java/com/android/internal/jank/Cuj.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.jank; + +import android.annotation.IntDef; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; + +/** @hide */ +public class Cuj { + @VisibleForTesting + public static final int MAX_LENGTH_OF_CUJ_NAME = 80; + + // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE. + public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0; + public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2; + public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3; + public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4; + public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5; + public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6; + public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7; + public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8; + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9; + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10; + public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11; + public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12; + public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13; + public static final int CUJ_NOTIFICATION_ADD = 14; + public static final int CUJ_NOTIFICATION_REMOVE = 15; + public static final int CUJ_NOTIFICATION_APP_START = 16; + public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17; + public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18; + public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19; + public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20; + public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21; + public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22; + public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23; + public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24; + public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25; + public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26; + public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27; + public static final int CUJ_SETTINGS_PAGE_SCROLL = 28; + public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33; + public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; + public static final int CUJ_PIP_TRANSITION = 35; + public static final int CUJ_WALLPAPER_TRANSITION = 36; + public static final int CUJ_USER_SWITCH = 37; + public static final int CUJ_SPLASHSCREEN_AVD = 38; + public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39; + public static final int CUJ_SCREEN_OFF = 40; + public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41; + public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42; + public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43; + public static final int CUJ_UNFOLD_ANIM = 44; + public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45; + public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46; + public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47; + public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48; + public static final int CUJ_SPLIT_SCREEN_ENTER = 49; + public static final int CUJ_SPLIT_SCREEN_EXIT = 50; + public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved. + public static final int CUJ_SPLIT_SCREEN_RESIZE = 52; + public static final int CUJ_SETTINGS_SLIDER = 53; + public static final int CUJ_TAKE_SCREENSHOT = 54; + public static final int CUJ_VOLUME_CONTROL = 55; + public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56; + public static final int CUJ_SETTINGS_TOGGLE = 57; + public static final int CUJ_SHADE_DIALOG_OPEN = 58; + public static final int CUJ_USER_DIALOG_OPEN = 59; + public static final int CUJ_TASKBAR_EXPAND = 60; + public static final int CUJ_TASKBAR_COLLAPSE = 61; + public static final int CUJ_SHADE_CLEAR_ALL = 62; + public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63; + public static final int CUJ_LOCKSCREEN_OCCLUSION = 64; + public static final int CUJ_RECENTS_SCROLLING = 65; + public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66; + public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67; + public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68; + public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70; + public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71; + // 72 - 77 are reserved for b/281564325. + + /** + * In some cases when we do not have any end-target, we play a simple slide-down animation. + * eg: Open an app from Overview/Task switcher such that there is no home-screen icon. + * eg: Exit the app using back gesture. + */ + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78; + // 79 is reserved. + public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80; + public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81; + + public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82; + + public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83; + + public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84; + public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85; + public static final int CUJ_PREDICTIVE_BACK_HOME = 86; + public static final int CUJ_LAUNCHER_SEARCH_QSB_OPEN = 87; + + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. + @VisibleForTesting + static final int LAST_CUJ = CUJ_LAUNCHER_SEARCH_QSB_OPEN; + + /** @hide */ + @IntDef({ + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, + CUJ_NOTIFICATION_SHADE_SCROLL_FLING, + CUJ_NOTIFICATION_SHADE_ROW_EXPAND, + CUJ_NOTIFICATION_SHADE_ROW_SWIPE, + CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, + CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, + CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS, + CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON, + CUJ_LAUNCHER_APP_CLOSE_TO_HOME, + CUJ_LAUNCHER_APP_CLOSE_TO_PIP, + CUJ_LAUNCHER_QUICK_SWITCH, + CUJ_NOTIFICATION_HEADS_UP_APPEAR, + CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, + CUJ_NOTIFICATION_ADD, + CUJ_NOTIFICATION_REMOVE, + CUJ_NOTIFICATION_APP_START, + CUJ_LOCKSCREEN_PASSWORD_APPEAR, + CUJ_LOCKSCREEN_PATTERN_APPEAR, + CUJ_LOCKSCREEN_PIN_APPEAR, + CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR, + CUJ_LOCKSCREEN_PATTERN_DISAPPEAR, + CUJ_LOCKSCREEN_PIN_DISAPPEAR, + CUJ_LOCKSCREEN_TRANSITION_FROM_AOD, + CUJ_LOCKSCREEN_TRANSITION_TO_AOD, + CUJ_LAUNCHER_OPEN_ALL_APPS, + CUJ_LAUNCHER_ALL_APPS_SCROLL, + CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET, + CUJ_SETTINGS_PAGE_SCROLL, + CUJ_LOCKSCREEN_UNLOCK_ANIMATION, + CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON, + CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER, + CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, + CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, + CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + CUJ_PIP_TRANSITION, + CUJ_WALLPAPER_TRANSITION, + CUJ_USER_SWITCH, + CUJ_SPLASHSCREEN_AVD, + CUJ_SPLASHSCREEN_EXIT_ANIM, + CUJ_SCREEN_OFF, + CUJ_SCREEN_OFF_SHOW_AOD, + CUJ_ONE_HANDED_ENTER_TRANSITION, + CUJ_ONE_HANDED_EXIT_TRANSITION, + CUJ_UNFOLD_ANIM, + CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS, + CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS, + CUJ_SUW_LOADING_TO_NEXT_FLOW, + CUJ_SUW_LOADING_SCREEN_FOR_STATUS, + CUJ_SPLIT_SCREEN_ENTER, + CUJ_SPLIT_SCREEN_EXIT, + CUJ_LOCKSCREEN_LAUNCH_CAMERA, + CUJ_SPLIT_SCREEN_RESIZE, + CUJ_SETTINGS_SLIDER, + CUJ_TAKE_SCREENSHOT, + CUJ_VOLUME_CONTROL, + CUJ_BIOMETRIC_PROMPT_TRANSITION, + CUJ_SETTINGS_TOGGLE, + CUJ_SHADE_DIALOG_OPEN, + CUJ_USER_DIALOG_OPEN, + CUJ_TASKBAR_EXPAND, + CUJ_TASKBAR_COLLAPSE, + CUJ_SHADE_CLEAR_ALL, + CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, + CUJ_LOCKSCREEN_OCCLUSION, + CUJ_RECENTS_SCROLLING, + CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS, + CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE, + CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME, + CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION, + CUJ_LAUNCHER_OPEN_SEARCH_RESULT, + CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK, + CUJ_IME_INSETS_SHOW_ANIMATION, + CUJ_IME_INSETS_HIDE_ANIMATION, + CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER, + CUJ_LAUNCHER_UNFOLD_ANIM, + CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY, + CUJ_PREDICTIVE_BACK_CROSS_TASK, + CUJ_PREDICTIVE_BACK_HOME, + CUJ_LAUNCHER_SEARCH_QSB_OPEN, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CujType { + } + + private static final int NO_STATSD_LOGGING = -1; + + // Used to convert CujType to InteractionType enum value for statsd logging. + // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd. + private static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1]; + static { + Arrays.fill(CUJ_TO_STATSD_INTERACTION_TYPE, NO_STATSD_LOGGING); + + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] = + FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN; + } + + private Cuj() { + } + + /** + * A helper method to translate CUJ type to CUJ name. + * + * @param cujType the cuj type defined in this file + * @return the name of the cuj type + */ + public static String getNameOfCuj(int cujType) { + // Please note: + // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME. + // 2. The returned string should be the same with the name defined in atoms.proto. + switch (cujType) { + case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE: + return "NOTIFICATION_SHADE_EXPAND_COLLAPSE"; + case CUJ_NOTIFICATION_SHADE_SCROLL_FLING: + return "NOTIFICATION_SHADE_SCROLL_FLING"; + case CUJ_NOTIFICATION_SHADE_ROW_EXPAND: + return "NOTIFICATION_SHADE_ROW_EXPAND"; + case CUJ_NOTIFICATION_SHADE_ROW_SWIPE: + return "NOTIFICATION_SHADE_ROW_SWIPE"; + case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE: + return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE"; + case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE: + return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE"; + case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS: + return "LAUNCHER_APP_LAUNCH_FROM_RECENTS"; + case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON: + return "LAUNCHER_APP_LAUNCH_FROM_ICON"; + case CUJ_LAUNCHER_APP_CLOSE_TO_HOME: + return "LAUNCHER_APP_CLOSE_TO_HOME"; + case CUJ_LAUNCHER_APP_CLOSE_TO_PIP: + return "LAUNCHER_APP_CLOSE_TO_PIP"; + case CUJ_LAUNCHER_QUICK_SWITCH: + return "LAUNCHER_QUICK_SWITCH"; + case CUJ_NOTIFICATION_HEADS_UP_APPEAR: + return "NOTIFICATION_HEADS_UP_APPEAR"; + case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR: + return "NOTIFICATION_HEADS_UP_DISAPPEAR"; + case CUJ_NOTIFICATION_ADD: + return "NOTIFICATION_ADD"; + case CUJ_NOTIFICATION_REMOVE: + return "NOTIFICATION_REMOVE"; + case CUJ_NOTIFICATION_APP_START: + return "NOTIFICATION_APP_START"; + case CUJ_LOCKSCREEN_PASSWORD_APPEAR: + return "LOCKSCREEN_PASSWORD_APPEAR"; + case CUJ_LOCKSCREEN_PATTERN_APPEAR: + return "LOCKSCREEN_PATTERN_APPEAR"; + case CUJ_LOCKSCREEN_PIN_APPEAR: + return "LOCKSCREEN_PIN_APPEAR"; + case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR: + return "LOCKSCREEN_PASSWORD_DISAPPEAR"; + case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR: + return "LOCKSCREEN_PATTERN_DISAPPEAR"; + case CUJ_LOCKSCREEN_PIN_DISAPPEAR: + return "LOCKSCREEN_PIN_DISAPPEAR"; + case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD: + return "LOCKSCREEN_TRANSITION_FROM_AOD"; + case CUJ_LOCKSCREEN_TRANSITION_TO_AOD: + return "LOCKSCREEN_TRANSITION_TO_AOD"; + case CUJ_LAUNCHER_OPEN_ALL_APPS : + return "LAUNCHER_OPEN_ALL_APPS"; + case CUJ_LAUNCHER_ALL_APPS_SCROLL: + return "LAUNCHER_ALL_APPS_SCROLL"; + case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET: + return "LAUNCHER_APP_LAUNCH_FROM_WIDGET"; + case CUJ_SETTINGS_PAGE_SCROLL: + return "SETTINGS_PAGE_SCROLL"; + case CUJ_LOCKSCREEN_UNLOCK_ANIMATION: + return "LOCKSCREEN_UNLOCK_ANIMATION"; + case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON: + return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON"; + case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER: + return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER"; + case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE: + return "SHADE_APP_LAUNCH_FROM_QS_TILE"; + case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON: + return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON"; + case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP: + return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP"; + case CUJ_PIP_TRANSITION: + return "PIP_TRANSITION"; + case CUJ_WALLPAPER_TRANSITION: + return "WALLPAPER_TRANSITION"; + case CUJ_USER_SWITCH: + return "USER_SWITCH"; + case CUJ_SPLASHSCREEN_AVD: + return "SPLASHSCREEN_AVD"; + case CUJ_SPLASHSCREEN_EXIT_ANIM: + return "SPLASHSCREEN_EXIT_ANIM"; + case CUJ_SCREEN_OFF: + return "SCREEN_OFF"; + case CUJ_SCREEN_OFF_SHOW_AOD: + return "SCREEN_OFF_SHOW_AOD"; + case CUJ_ONE_HANDED_ENTER_TRANSITION: + return "ONE_HANDED_ENTER_TRANSITION"; + case CUJ_ONE_HANDED_EXIT_TRANSITION: + return "ONE_HANDED_EXIT_TRANSITION"; + case CUJ_UNFOLD_ANIM: + return "UNFOLD_ANIM"; + case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS: + return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS"; + case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS: + return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS"; + case CUJ_SUW_LOADING_TO_NEXT_FLOW: + return "SUW_LOADING_TO_NEXT_FLOW"; + case CUJ_SUW_LOADING_SCREEN_FOR_STATUS: + return "SUW_LOADING_SCREEN_FOR_STATUS"; + case CUJ_SPLIT_SCREEN_ENTER: + return "SPLIT_SCREEN_ENTER"; + case CUJ_SPLIT_SCREEN_EXIT: + return "SPLIT_SCREEN_EXIT"; + case CUJ_LOCKSCREEN_LAUNCH_CAMERA: + return "LOCKSCREEN_LAUNCH_CAMERA"; + case CUJ_SPLIT_SCREEN_RESIZE: + return "SPLIT_SCREEN_RESIZE"; + case CUJ_SETTINGS_SLIDER: + return "SETTINGS_SLIDER"; + case CUJ_TAKE_SCREENSHOT: + return "TAKE_SCREENSHOT"; + case CUJ_VOLUME_CONTROL: + return "VOLUME_CONTROL"; + case CUJ_BIOMETRIC_PROMPT_TRANSITION: + return "BIOMETRIC_PROMPT_TRANSITION"; + case CUJ_SETTINGS_TOGGLE: + return "SETTINGS_TOGGLE"; + case CUJ_SHADE_DIALOG_OPEN: + return "SHADE_DIALOG_OPEN"; + case CUJ_USER_DIALOG_OPEN: + return "USER_DIALOG_OPEN"; + case CUJ_TASKBAR_EXPAND: + return "TASKBAR_EXPAND"; + case CUJ_TASKBAR_COLLAPSE: + return "TASKBAR_COLLAPSE"; + case CUJ_SHADE_CLEAR_ALL: + return "SHADE_CLEAR_ALL"; + case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION: + return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION"; + case CUJ_LOCKSCREEN_OCCLUSION: + return "LOCKSCREEN_OCCLUSION"; + case CUJ_RECENTS_SCROLLING: + return "RECENTS_SCROLLING"; + case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS: + return "LAUNCHER_APP_SWIPE_TO_RECENTS"; + case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE: + return "LAUNCHER_CLOSE_ALL_APPS_SWIPE"; + case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME: + return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME"; + case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION: + return "LOCKSCREEN_CLOCK_MOVE_ANIMATION"; + case CUJ_LAUNCHER_OPEN_SEARCH_RESULT: + return "LAUNCHER_OPEN_SEARCH_RESULT"; + case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK: + return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK"; + case CUJ_IME_INSETS_SHOW_ANIMATION: + return "IME_INSETS_SHOW_ANIMATION"; + case CUJ_IME_INSETS_HIDE_ANIMATION: + return "IME_INSETS_HIDE_ANIMATION"; + case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER: + return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER"; + case CUJ_LAUNCHER_UNFOLD_ANIM: + return "LAUNCHER_UNFOLD_ANIM"; + case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY: + return "PREDICTIVE_BACK_CROSS_ACTIVITY"; + case CUJ_PREDICTIVE_BACK_CROSS_TASK: + return "PREDICTIVE_BACK_CROSS_TASK"; + case CUJ_PREDICTIVE_BACK_HOME: + return "PREDICTIVE_BACK_HOME"; + case CUJ_LAUNCHER_SEARCH_QSB_OPEN: + return "LAUNCHER_SEARCH_QSB_OPEN"; + } + return "UNKNOWN"; + } + + public static int getStatsdInteractionType(@CujType int cujType) { + return CUJ_TO_STATSD_INTERACTION_TYPE[cujType]; + } + + /** Returns whether the measurements for the given CUJ should be written to statsd. */ + public static boolean logToStatsd(@CujType int cujType) { + return getStatsdInteractionType(cujType) != NO_STATSD_LOGGING; + } + + /** + * A helper method to translate interaction type to CUJ name. + * + * @param interactionType the interaction type defined in AtomsProto.java + * @return the name of the interaction type + */ + public static String getNameOfInteraction(int interactionType) { + // There is an offset amount of 1 between cujType and interactionType. + return Cuj.getNameOfCuj(getCujTypeFromInteraction(interactionType)); + } + + /** + * A helper method to translate interaction type to CUJ type. + * + * @param interactionType the interaction type defined in AtomsProto.java + * @return the integer in {@link Cuj.CujType} + */ + private static int getCujTypeFromInteraction(int interactionType) { + return interactionType - 1; + } +} diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index c83452d88290..86729f74cd85 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -53,7 +53,6 @@ import android.view.WindowCallbacks; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.DisplayRefreshRate.RefreshRate; import com.android.internal.jank.InteractionJankMonitor.Configuration; -import com.android.internal.jank.InteractionJankMonitor.Session; import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; @@ -67,7 +66,6 @@ import java.util.concurrent.TimeUnit; public class FrameTracker extends SurfaceControl.OnJankDataListener implements HardwareRendererObserver.OnFrameMetricsAvailableListener { private static final String TAG = "FrameTracker"; - private static final boolean DEBUG = false; private static final long INVALID_ID = -1; public static final int NANOS_IN_MILLISECOND = 1_000_000; @@ -93,20 +91,19 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener REASON_CANCEL_NORMAL, REASON_CANCEL_NOT_BEGUN, REASON_CANCEL_SAME_VSYNC, + REASON_CANCEL_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) public @interface Reasons { } - @VisibleForTesting - public final InteractionJankMonitor mMonitor; private final HardwareRendererObserver mObserver; private final int mTraceThresholdMissedFrames; private final int mTraceThresholdFrameTimeMillis; private final ThreadedRendererWrapper mRendererWrapper; private final FrameMetricsWrapper mMetricsWrapper; private final SparseArray<JankInfo> mJankInfos = new SparseArray<>(); - private final Session mSession; + private final Configuration mConfig; private final ViewRootWrapper mViewRoot; private final SurfaceControlWrapper mSurfaceControlWrapper; private final int mDisplayId; @@ -197,19 +194,18 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } } - public FrameTracker(@NonNull InteractionJankMonitor monitor, @NonNull Session session, - @NonNull Handler handler, @Nullable ThreadedRendererWrapper renderer, + public FrameTracker(@NonNull Configuration config, + @Nullable ThreadedRendererWrapper renderer, @Nullable ViewRootWrapper viewRootWrapper, @NonNull SurfaceControlWrapper surfaceControlWrapper, @NonNull ChoreographerWrapper choreographer, @Nullable FrameMetricsWrapper metrics, @NonNull StatsLogWrapper statsLog, int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis, - @Nullable FrameTrackerListener listener, @NonNull Configuration config) { - mMonitor = monitor; + @Nullable FrameTrackerListener listener) { mSurfaceOnly = config.isSurfaceOnly(); - mSession = session; - mHandler = handler; + mConfig = config; + mHandler = config.getHandler(); mChoreographer = choreographer; mSurfaceControlWrapper = surfaceControlWrapper; mStatsLog = statsLog; @@ -222,7 +218,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mObserver = mSurfaceOnly ? null : new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), - handler, /* waitForPresentTime= */ false); + mHandler, /* waitForPresentTime= */ false); mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; @@ -242,7 +238,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() { @Override public void surfaceCreated(SurfaceControl.Transaction t) { - getHandler().runWithScissors(() -> { + mHandler.runWithScissors(() -> { if (mSurfaceControl == null) { mSurfaceControl = mViewRoot.getSurfaceControl(); if (mBeginVsyncId != INVALID_ID) { @@ -262,13 +258,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener // Wait a while to give the system a chance for the remaining // frames to arrive, then force finish the session. - getHandler().postDelayed(() -> { - if (DEBUG) { - Log.d(TAG, "surfaceDestroyed: " + mSession.getName() - + ", finalized=" + mMetricsFinalized - + ", info=" + mJankInfos.size() - + ", vsync=" + mBeginVsyncId); - } + mHandler.postDelayed(() -> { if (!mMetricsFinalized) { end(REASON_END_SURFACE_DESTROYED); finish(); @@ -282,11 +272,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } } - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public Handler getHandler() { - return mHandler; - } - /** * Begin a trace session of the CUJ. */ @@ -300,10 +285,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync; } if (mSurfaceControl != null) { - if (DEBUG) { - Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId - + ", defer=" + mDeferMonitoring + ", current=" + currentVsync); - } if (mDeferMonitoring && currentVsync < mBeginVsyncId) { markEvent("FT#deferMonitoring", 0); // Normal case, we begin the instrument from the very beginning, @@ -314,11 +295,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener // there is no need to skip the frame where the begin invocation happens. beginInternal(); } - } else { - if (DEBUG) { - Log.d(TAG, "begin: defer beginning since the surface is not ready for CUJ=" - + mSession.getName()); - } } } @@ -336,8 +312,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener return; } mTracingStarted = true; - Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, mSession.getName(), mSession.getName(), - (int) mBeginVsyncId); + String name = mConfig.getSessionName(); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId); markEvent("FT#beginVsync", mBeginVsyncId); markEvent("FT#layerId", mSurfaceControl.getLayerId()); mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); @@ -361,15 +337,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } else if (mEndVsyncId <= mBeginVsyncId) { return cancel(REASON_CANCEL_SAME_VSYNC); } else { - if (DEBUG) { - Log.d(TAG, "end: " + mSession.getName() - + ", end=" + mEndVsyncId + ", reason=" + reason); - } + final String name = mConfig.getSessionName(); markEvent("FT#end", reason); markEvent("FT#endVsync", mEndVsyncId); - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(), - (int) mBeginVsyncId); - mSession.setReason(reason); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, name, (int) mBeginVsyncId); // We don't remove observer here, // will remove it when all the frame metrics in this duration are called back. @@ -395,16 +366,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mFlushAttempts++; } else { mWaitForFinishTimedOut = () -> { - Log.e(TAG, "force finish cuj, time out: " + mSession.getName()); + Log.e(TAG, "force finish cuj, time out: " + name); finish(); }; delay = TimeUnit.SECONDS.toMillis(10); } - getHandler().postDelayed(mWaitForFinishTimedOut, delay); + mHandler.postDelayed(mWaitForFinishTimedOut, delay); } }; - getHandler().postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND); - notifyCujEvent(ACTION_SESSION_END); + mHandler.postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND); + notifyCujEvent(ACTION_SESSION_END, reason); return true; } } @@ -421,22 +392,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener markEvent("FT#cancel", reason); // We don't need to end the trace section if it has never begun. if (mTracingStarted) { - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(), - (int) mBeginVsyncId); + Trace.asyncTraceForTrackEnd( + Trace.TRACE_TAG_APP, mConfig.getSessionName(), (int) mBeginVsyncId); } // Always remove the observers in cancel call to avoid leakage. removeObservers(); - if (DEBUG) { - Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId - + ", end=" + mEndVsyncId + ", reason=" + reason); - } - - mSession.setReason(reason); // Notify the listener the session has been cancelled. // We don't notify the listeners if the session never begun. - notifyCujEvent(ACTION_SESSION_CANCEL); + notifyCujEvent(ACTION_SESSION_CANCEL, reason); return true; } @@ -455,13 +420,13 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener "The length of the trace event description <%s> exceeds %d", event, MAX_LENGTH_EVENT_DESC)); } - Trace.instantForTrack(Trace.TRACE_TAG_APP, mSession.getName(), event); + Trace.instantForTrack(Trace.TRACE_TAG_APP, mConfig.getSessionName(), event); } } - private void notifyCujEvent(String action) { + private void notifyCujEvent(String action, @Reasons int reason) { if (mListener == null) return; - mListener.onCujEvents(mSession, action); + mListener.onCujEvents(this, action, reason); } @Override @@ -496,7 +461,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ @VisibleForTesting public void postCallback(Runnable callback) { - getHandler().post(callback); + mHandler.post(callback); } @Nullable @@ -587,13 +552,15 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (mMetricsFinalized || mCancelled) return; mMetricsFinalized = true; - getHandler().removeCallbacks(mWaitForFinishTimedOut); + mHandler.removeCallbacks(mWaitForFinishTimedOut); mWaitForFinishTimedOut = null; markEvent("FT#finish", mJankInfos.size()); // The tracing has been ended, remove the observer, see if need to trigger perfetto. removeObservers(); + final String name = mConfig.getSessionName(); + int totalFramesCount = 0; long maxFrameTimeNanos = 0; int missedFramesCount = 0; @@ -616,7 +583,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener totalFramesCount++; boolean missedFrame = false; if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) { - Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + mSession.getName()); + Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name); missedAppFramesCount++; missedFrame = true; } @@ -625,7 +592,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0 || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0 || (info.jankType & PREDICTION_ERROR) != 0) { - Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + mSession.getName()); + Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name); missedSfFramesCount++; missedFrame = true; } @@ -646,7 +613,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (!mSurfaceOnly && !info.hwuiCallbackFired) { markEvent("FT#MissedHWUICallback", info.frameVsyncId); Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId - + ", CUJ=" + mSession.getName()); + + ", CUJ=" + name); } } if (!mSurfaceOnly && info.hwuiCallbackFired) { @@ -654,7 +621,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (!info.surfaceControlCallbackFired) { markEvent("FT#MissedSFCallback", info.frameVsyncId); Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId - + ", CUJ=" + mSession.getName()); + + ", CUJ=" + name); } } } @@ -662,29 +629,26 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener maxSuccessiveMissedFramesCount, successiveMissedFramesCount); // Log the frame stats as counters to make them easily accessible in traces. - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames", - missedFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames", - missedAppFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames", - missedSfFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames", - totalFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis", + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedFrames", missedFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedAppFrames", missedAppFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedSfFrames", missedSfFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#totalFrames", totalFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxFrameTimeMillis", (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND)); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxSuccessiveMissedFrames", + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxSuccessiveMissedFrames", maxSuccessiveMissedFramesCount); // Trigger perfetto if necessary. - if (shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) { - triggerPerfetto(); + if (mListener != null + && shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) { + mListener.triggerPerfetto(mConfig); } - if (mSession.logToStatsd()) { + if (mConfig.logToStatsd()) { mStatsLog.write( FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED, mDisplayId, refreshRate, - mSession.getStatsdInteractionType(), + mConfig.getStatsdInteractionType(), totalFramesCount, missedFramesCount, maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */ @@ -692,16 +656,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener missedAppFramesCount, maxSuccessiveMissedFramesCount); } - if (DEBUG) { - Log.i(TAG, "finish: CUJ=" + mSession.getName() - + " (" + mBeginVsyncId + "," + mEndVsyncId + ")" - + " totalFrames=" + totalFramesCount - + " missedAppFrames=" + missedAppFramesCount - + " missedSfFrames=" + missedSfFramesCount - + " missedFrames=" + missedFramesCount - + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND - + " maxSuccessiveMissedFramesCount=" + maxSuccessiveMissedFramesCount); - } } ThreadedRendererWrapper getThreadedRenderer() { @@ -737,13 +691,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } /** - * Trigger the prefetto daemon. - */ - public void triggerPerfetto() { - mMonitor.trigger(mSession); - } - - /** * A wrapper class that we can spy FrameMetrics (a final class) in unit tests. */ public static class FrameMetricsWrapper { @@ -895,9 +842,17 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener /** * Notify that the CUJ session was created. * - * @param session the CUJ session + * @param tracker the tracker * @param action the specific action + * @param reason the reason for the action + */ + void onCujEvents(FrameTracker tracker, String action, @Reasons int reason); + + /** + * Notify that the Perfetto trace should be triggered. + * + * @param config the tracker configuration */ - void onCujEvents(Session session, String action); + void triggerPerfetto(Configuration config); } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index e6b036cfaa19..8b1879f5225d 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -23,89 +23,9 @@ import static android.provider.DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; -import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; import android.Manifest; import android.annotation.ColorInt; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.UiThread; @@ -137,34 +57,31 @@ import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.util.PerfettoTrigger; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.time.Instant; -import java.util.Locale; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; /** - * This class let users to begin and end the always on tracing mechanism. + * This class lets users begin and end the always on tracing mechanism. * * Enabling for local development: - * + *<pre> * adb shell device_config put interaction_jank_monitor enabled true * adb shell device_config put interaction_jank_monitor sampling_interval 1 - * + * </pre> * On debuggable builds, an overlay can be used to display the name of the * currently running cuj using: - * + * <pre> * adb shell device_config put interaction_jank_monitor debug_overlay_enabled true - * - * NOTE: The overlay will interfere with metrics, so it should only be used - * for understanding which UI events correspeond to which CUJs. + * </pre> + * <b>NOTE</b>: The overlay will interfere with metrics, so it should only be used + * for understanding which UI events correspond to which CUJs. * * @hide */ public class InteractionJankMonitor { private static final String TAG = InteractionJankMonitor.class.getSimpleName(); - private static final boolean DEBUG = false; private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; @@ -186,218 +103,79 @@ public class InteractionJankMonitor { private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false; - @VisibleForTesting - public static final int MAX_LENGTH_OF_CUJ_NAME = 80; private static final int MAX_LENGTH_SESSION_NAME = 100; public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL"; - // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE. - public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0; - public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2; - public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3; - public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4; - public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5; - public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6; - public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7; - public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8; - public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9; - public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10; - public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11; - public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12; - public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13; - public static final int CUJ_NOTIFICATION_ADD = 14; - public static final int CUJ_NOTIFICATION_REMOVE = 15; - public static final int CUJ_NOTIFICATION_APP_START = 16; - public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17; - public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18; - public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19; - public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20; - public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21; - public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22; - public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23; - public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24; - public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25; - public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26; - public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27; - public static final int CUJ_SETTINGS_PAGE_SCROLL = 28; - public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29; - public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30; - public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31; - public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32; - public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33; - public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; - public static final int CUJ_PIP_TRANSITION = 35; - public static final int CUJ_WALLPAPER_TRANSITION = 36; - public static final int CUJ_USER_SWITCH = 37; - public static final int CUJ_SPLASHSCREEN_AVD = 38; - public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39; - public static final int CUJ_SCREEN_OFF = 40; - public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41; - public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42; - public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43; - public static final int CUJ_UNFOLD_ANIM = 44; - public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45; - public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46; - public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47; - public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48; - public static final int CUJ_SPLIT_SCREEN_ENTER = 49; - public static final int CUJ_SPLIT_SCREEN_EXIT = 50; - public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved. - public static final int CUJ_SPLIT_SCREEN_RESIZE = 52; - public static final int CUJ_SETTINGS_SLIDER = 53; - public static final int CUJ_TAKE_SCREENSHOT = 54; - public static final int CUJ_VOLUME_CONTROL = 55; - public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56; - public static final int CUJ_SETTINGS_TOGGLE = 57; - public static final int CUJ_SHADE_DIALOG_OPEN = 58; - public static final int CUJ_USER_DIALOG_OPEN = 59; - public static final int CUJ_TASKBAR_EXPAND = 60; - public static final int CUJ_TASKBAR_COLLAPSE = 61; - public static final int CUJ_SHADE_CLEAR_ALL = 62; - public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63; - public static final int CUJ_LOCKSCREEN_OCCLUSION = 64; - public static final int CUJ_RECENTS_SCROLLING = 65; - public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66; - public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67; - public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68; - public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70; - public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71; - // 72 - 77 are reserved for b/281564325. - - /** - * In some cases when we do not have any end-target, we play a simple slide-down animation. - * eg: Open an app from Overview/Task switcher such that there is no home-screen icon. - * eg: Exit the app using back gesture. - */ - public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78; - public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80; - public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81; - - public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82; - - public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83; - - public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84; - public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85; - public static final int CUJ_PREDICTIVE_BACK_HOME = 86; - - private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME; - private static final int NO_STATSD_LOGGING = -1; - - // Used to convert CujType to InteractionType enum value for statsd logging. - // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd. - @VisibleForTesting - public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1]; - - static { - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; - CUJ_TO_STATSD_INTERACTION_TYPE[1] = NO_STATSD_LOGGING; // This is deprecated. - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME; - CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated. - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT; - // 72 - 77 are reserved for b/281564325. - CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; - CUJ_TO_STATSD_INTERACTION_TYPE[79] = NO_STATSD_LOGGING; // This is deprecated. - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = - UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = - UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = - UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME; - } + // These are not the CUJ constants you are looking for. These constants simply forward their + // definition from {@link Cuj}. They are here only as a transition measure until all references + // have been updated to the new location. + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE; + @Deprecated public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME; + @Deprecated public static final int CUJ_LAUNCHER_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH; + @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR; + @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR; + @Deprecated public static final int CUJ_NOTIFICATION_ADD = Cuj.CUJ_NOTIFICATION_ADD; + @Deprecated public static final int CUJ_NOTIFICATION_REMOVE = Cuj.CUJ_NOTIFICATION_REMOVE; + @Deprecated public static final int CUJ_NOTIFICATION_APP_START = Cuj.CUJ_NOTIFICATION_APP_START; + @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PIN_APPEAR = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; + @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; + @Deprecated public static final int CUJ_SETTINGS_PAGE_SCROLL = Cuj.CUJ_SETTINGS_PAGE_SCROLL; + @Deprecated public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = Cuj.CUJ_LOCKSCREEN_UNLOCK_ANIMATION; + @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; + @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; + @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE; + @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; + @Deprecated public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; + @Deprecated public static final int CUJ_PIP_TRANSITION = Cuj.CUJ_PIP_TRANSITION; + @Deprecated public static final int CUJ_USER_SWITCH = Cuj.CUJ_USER_SWITCH; + @Deprecated public static final int CUJ_SPLASHSCREEN_AVD = Cuj.CUJ_SPLASHSCREEN_AVD; + @Deprecated public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = Cuj.CUJ_SPLASHSCREEN_EXIT_ANIM; + @Deprecated public static final int CUJ_SCREEN_OFF = Cuj.CUJ_SCREEN_OFF; + @Deprecated public static final int CUJ_SCREEN_OFF_SHOW_AOD = Cuj.CUJ_SCREEN_OFF_SHOW_AOD; + @Deprecated public static final int CUJ_UNFOLD_ANIM = Cuj.CUJ_UNFOLD_ANIM; + @Deprecated public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = Cuj.CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; + @Deprecated public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = Cuj.CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; + @Deprecated public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = Cuj.CUJ_SUW_LOADING_TO_NEXT_FLOW; + @Deprecated public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = Cuj.CUJ_SUW_LOADING_SCREEN_FOR_STATUS; + @Deprecated public static final int CUJ_SPLIT_SCREEN_RESIZE = Cuj.CUJ_SPLIT_SCREEN_RESIZE; + @Deprecated public static final int CUJ_SETTINGS_SLIDER = Cuj.CUJ_SETTINGS_SLIDER; + @Deprecated public static final int CUJ_TAKE_SCREENSHOT = Cuj.CUJ_TAKE_SCREENSHOT; + @Deprecated public static final int CUJ_VOLUME_CONTROL = Cuj.CUJ_VOLUME_CONTROL; + @Deprecated public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = Cuj.CUJ_BIOMETRIC_PROMPT_TRANSITION; + @Deprecated public static final int CUJ_SETTINGS_TOGGLE = Cuj.CUJ_SETTINGS_TOGGLE; + @Deprecated public static final int CUJ_SHADE_DIALOG_OPEN = Cuj.CUJ_SHADE_DIALOG_OPEN; + @Deprecated public static final int CUJ_USER_DIALOG_OPEN = Cuj.CUJ_USER_DIALOG_OPEN; + @Deprecated public static final int CUJ_TASKBAR_EXPAND = Cuj.CUJ_TASKBAR_EXPAND; + @Deprecated public static final int CUJ_TASKBAR_COLLAPSE = Cuj.CUJ_TASKBAR_COLLAPSE; + @Deprecated public static final int CUJ_SHADE_CLEAR_ALL = Cuj.CUJ_SHADE_CLEAR_ALL; + @Deprecated public static final int CUJ_LOCKSCREEN_OCCLUSION = Cuj.CUJ_LOCKSCREEN_OCCLUSION; + @Deprecated public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = Cuj.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; + @Deprecated public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = Cuj.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; + @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY; + @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK; + @Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME; private static class InstanceHolder { public static final InteractionJankMonitor INSTANCE = new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME)); } - private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = - this::updateProperties; - @GuardedBy("mLock") - private final SparseArray<FrameTracker> mRunningTrackers; - @GuardedBy("mLock") - private final SparseArray<Runnable> mTimeoutActions; - private final HandlerThread mWorker; + private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>(); + private final Handler mWorker; private final DisplayResolutionTracker mDisplayResolutionTracker; private final Object mLock = new Object(); private @ColorInt int mDebugBgColor = Color.CYAN; @@ -409,91 +187,6 @@ public class InteractionJankMonitor { private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES; private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS; - /** @hide */ - @IntDef({ - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, - CUJ_NOTIFICATION_SHADE_SCROLL_FLING, - CUJ_NOTIFICATION_SHADE_ROW_EXPAND, - CUJ_NOTIFICATION_SHADE_ROW_SWIPE, - CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, - CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, - CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS, - CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON, - CUJ_LAUNCHER_APP_CLOSE_TO_HOME, - CUJ_LAUNCHER_APP_CLOSE_TO_PIP, - CUJ_LAUNCHER_QUICK_SWITCH, - CUJ_NOTIFICATION_HEADS_UP_APPEAR, - CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, - CUJ_NOTIFICATION_ADD, - CUJ_NOTIFICATION_REMOVE, - CUJ_NOTIFICATION_APP_START, - CUJ_LOCKSCREEN_PASSWORD_APPEAR, - CUJ_LOCKSCREEN_PATTERN_APPEAR, - CUJ_LOCKSCREEN_PIN_APPEAR, - CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR, - CUJ_LOCKSCREEN_PATTERN_DISAPPEAR, - CUJ_LOCKSCREEN_PIN_DISAPPEAR, - CUJ_LOCKSCREEN_TRANSITION_FROM_AOD, - CUJ_LOCKSCREEN_TRANSITION_TO_AOD, - CUJ_LAUNCHER_OPEN_ALL_APPS, - CUJ_LAUNCHER_ALL_APPS_SCROLL, - CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET, - CUJ_SETTINGS_PAGE_SCROLL, - CUJ_LOCKSCREEN_UNLOCK_ANIMATION, - CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON, - CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER, - CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, - CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, - CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, - CUJ_PIP_TRANSITION, - CUJ_WALLPAPER_TRANSITION, - CUJ_USER_SWITCH, - CUJ_SPLASHSCREEN_AVD, - CUJ_SPLASHSCREEN_EXIT_ANIM, - CUJ_SCREEN_OFF, - CUJ_SCREEN_OFF_SHOW_AOD, - CUJ_ONE_HANDED_ENTER_TRANSITION, - CUJ_ONE_HANDED_EXIT_TRANSITION, - CUJ_UNFOLD_ANIM, - CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS, - CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS, - CUJ_SUW_LOADING_TO_NEXT_FLOW, - CUJ_SUW_LOADING_SCREEN_FOR_STATUS, - CUJ_SPLIT_SCREEN_ENTER, - CUJ_SPLIT_SCREEN_EXIT, - CUJ_LOCKSCREEN_LAUNCH_CAMERA, - CUJ_SPLIT_SCREEN_RESIZE, - CUJ_SETTINGS_SLIDER, - CUJ_TAKE_SCREENSHOT, - CUJ_VOLUME_CONTROL, - CUJ_BIOMETRIC_PROMPT_TRANSITION, - CUJ_SETTINGS_TOGGLE, - CUJ_SHADE_DIALOG_OPEN, - CUJ_USER_DIALOG_OPEN, - CUJ_TASKBAR_EXPAND, - CUJ_TASKBAR_COLLAPSE, - CUJ_SHADE_CLEAR_ALL, - CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, - CUJ_LOCKSCREEN_OCCLUSION, - CUJ_RECENTS_SCROLLING, - CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS, - CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE, - CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME, - CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION, - CUJ_LAUNCHER_OPEN_SEARCH_RESULT, - CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK, - CUJ_IME_INSETS_SHOW_ANIMATION, - CUJ_IME_INSETS_HIDE_ANIMATION, - CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER, - CUJ_LAUNCHER_UNFOLD_ANIM, - CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY, - CUJ_PREDICTIVE_BACK_CROSS_TASK, - CUJ_PREDICTIVE_BACK_HOME, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface CujType { - } - /** * Get the singleton of InteractionJankMonitor. * @@ -511,71 +204,44 @@ public class InteractionJankMonitor { @VisibleForTesting @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public InteractionJankMonitor(@NonNull HandlerThread worker) { - mRunningTrackers = new SparseArray<>(); - mTimeoutActions = new SparseArray<>(); - mWorker = worker; - mWorker.start(); - mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler()); - mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; - mEnabled = DEFAULT_ENABLED; + worker.start(); + mWorker = worker.getThreadHandler(); + mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker); final Context context = ActivityThread.currentApplication(); - if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) { - if (DEBUG) { - Log.d(TAG, "Initialized the InteractionJankMonitor." - + " (No READ_DEVICE_CONFIG permission to change configs)" - + " enabled=" + mEnabled + ", interval=" + mSamplingInterval - + ", missedFrameThreshold=" + mTraceThresholdMissedFrames - + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis - + ", package=" + context.getPackageName()); - } + if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) { + Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission." + + " enabled=" + mEnabled + ", interval=" + mSamplingInterval + + ", missedFrameThreshold=" + mTraceThresholdMissedFrames + + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis + + ", package=" + (context == null ? "null" : context.getPackageName())); return; } // Post initialization to the background in case we're running on the main thread. - mWorker.getThreadHandler().post( - () -> { - try { - mPropertiesChangedListener.onPropertiesChanged( - DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR)); - DeviceConfig.addOnPropertiesChangedListener( - NAMESPACE_INTERACTION_JANK_MONITOR, - new HandlerExecutor(mWorker.getThreadHandler()), - mPropertiesChangedListener); - } catch (SecurityException ex) { - Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted=" - + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) - + ", package=" + context.getPackageName()); - } - }); + mWorker.post(() -> { + try { + updateProperties(DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR)); + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_INTERACTION_JANK_MONITOR, + new HandlerExecutor(mWorker), this::updateProperties); + } catch (SecurityException ex) { + Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted=" + + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) + + ", package=" + context.getPackageName()); + } + }); } /** * Creates a {@link FrameTracker} instance. * - * @param config the config used in instrumenting - * @param session the session associates with this tracker + * @param config the conifg associates with this tracker * @return instance of the FrameTracker */ @VisibleForTesting - public FrameTracker createFrameTracker(Configuration config, Session session) { + public FrameTracker createFrameTracker(Configuration config) { final View view = config.mView; - if (!config.hasValidView()) { - boolean attached = false; - boolean hasViewRoot = false; - boolean hasRenderer = false; - if (view != null) { - attached = view.isAttachedToWindow(); - hasViewRoot = view.getViewRootImpl() != null; - hasRenderer = view.getThreadedRenderer() != null; - } - Log.d(TAG, "create FrameTracker fails: view=" + view - + ", attached=" + attached + ", hasViewRoot=" + hasViewRoot - + ", hasRenderer=" + hasRenderer, new Throwable()); - return null; - } - final ThreadedRendererWrapper threadedRenderer = view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer()); final ViewRootWrapper viewRoot = @@ -583,52 +249,50 @@ public class InteractionJankMonitor { final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper(); final ChoreographerWrapper choreographer = new ChoreographerWrapper(Choreographer.getInstance()); - final FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s); + final FrameTrackerListener eventsListener = new FrameTrackerListener() { + @Override + public void onCujEvents(FrameTracker tracker, String action, int reason) { + config.getHandler().runWithScissors(() -> + handleCujEvents(config.mCujType, tracker, action, reason), + EXECUTOR_TASK_TIMEOUT); + } + + @Override + public void triggerPerfetto(Configuration config) { + mWorker.post(() -> PerfettoTrigger.trigger(config.getPerfettoTrigger())); + } + }; final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper(); - return new FrameTracker(this, session, config.getHandler(), threadedRenderer, viewRoot, + return new FrameTracker(config, threadedRenderer, viewRoot, surfaceControl, choreographer, frameMetrics, new FrameTracker.StatsLogWrapper(mDisplayResolutionTracker), mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, - eventsListener, config); + eventsListener); } @UiThread - private void handleCujEvents(String action, Session session) { + private void handleCujEvents( + @Cuj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason) { // Clear the running and timeout tasks if the end / cancel was fired within the tracker. // Or we might have memory leaks. - if (needRemoveTasks(action, session)) { - getTracker(session.getCuj()).getHandler().runWithScissors(() -> { - removeTimeout(session.getCuj()); - removeTracker(session.getCuj(), session.getReason()); - }, EXECUTOR_TASK_TIMEOUT); + if (needRemoveTasks(action, reason)) { + removeTrackerIfCurrent(cuj, tracker, reason); } } - private boolean needRemoveTasks(String action, Session session) { - final boolean badEnd = action.equals(ACTION_SESSION_END) - && session.getReason() != REASON_END_NORMAL; + private static boolean needRemoveTasks(String action, @Reasons int reason) { + final boolean badEnd = action.equals(ACTION_SESSION_END) && reason != REASON_END_NORMAL; final boolean badCancel = action.equals(ACTION_SESSION_CANCEL) - && !(session.getReason() == REASON_CANCEL_NORMAL - || session.getReason() == REASON_CANCEL_TIMEOUT); + && !(reason == REASON_CANCEL_NORMAL || reason == REASON_CANCEL_TIMEOUT); return badEnd || badCancel; } - private void removeTimeout(@CujType int cujType) { - synchronized (mLock) { - Runnable timeout = mTimeoutActions.get(cujType); - if (timeout != null) { - getTracker(cujType).getHandler().removeCallbacks(timeout); - mTimeoutActions.remove(cujType); - } - } - } - /** * @param cujType cuj type * @return true if the cuj is under instrumenting, false otherwise. */ - public boolean isInstrumenting(@CujType int cujType) { + public boolean isInstrumenting(@Cuj.CujType int cujType) { synchronized (mLock) { return mRunningTrackers.contains(cujType); } @@ -638,10 +302,10 @@ public class InteractionJankMonitor { * Begins a trace session. * * @param v an attached view. - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. * @return boolean true if the tracker is started successfully, false otherwise. */ - public boolean begin(View v, @CujType int cujType) { + public boolean begin(View v, @Cuj.CujType int cujType) { try { return begin(Configuration.Builder.withView(cujType, v)); } catch (IllegalArgumentException ex) { @@ -667,7 +331,7 @@ public class InteractionJankMonitor { final boolean success = config.getHandler().runWithScissors( () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT); if (!success) { - Log.d(TAG, "begin failed due to timeout, CUJ=" + getNameOfCuj(config.mCujType)); + Log.d(TAG, "begin failed due to timeout, CUJ=" + Cuj.getNameOfCuj(config.mCujType)); return false; } return result.mResult; @@ -680,75 +344,59 @@ public class InteractionJankMonitor { @UiThread private boolean beginInternal(@NonNull Configuration conf) { int cujType = conf.mCujType; - if (!shouldMonitor(cujType)) return false; - FrameTracker tracker = getTracker(cujType); - // Skip subsequent calls if we already have an ongoing tracing. - if (tracker != null) return false; + if (!shouldMonitor()) { + return false; + } - // begin a new trace session. - tracker = createFrameTracker(conf, new Session(cujType, conf.mTag)); - if (tracker == null) return false; - putTracker(cujType, tracker); - tracker.begin(); + RunningTracker tracker = putTrackerIfNoCurrent(cujType, () -> + new RunningTracker( + conf, createFrameTracker(conf), () -> cancel(cujType, REASON_CANCEL_TIMEOUT))); + if (tracker == null) { + return false; + } + tracker.mTracker.begin(); // Cancel the trace if we don't get an end() call in specified duration. - scheduleTimeoutAction( - cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT)); + scheduleTimeoutAction(tracker.mConfig, tracker.mTimeoutAction); + return true; } /** * Check if the monitoring is enabled and if it should be sampled. */ - @SuppressWarnings("RandomModInteger") @VisibleForTesting - public boolean shouldMonitor(@CujType int cujType) { - boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; - if (!mEnabled || !shouldSample) { - if (DEBUG) { - Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType) - + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED - + ", sample=" + shouldSample + ", interval=" + mSamplingInterval); - } - return false; - } - return true; + public boolean shouldMonitor() { + return mEnabled && (ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0); } - /** - * Schedules a timeout action. - * @param cuj cuj type - * @param timeout duration to timeout - * @param action action once timeout - */ @VisibleForTesting - public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) { - synchronized (mLock) { - mTimeoutActions.put(cuj, action); - getTracker(cuj).getHandler().postDelayed(action, timeout); - } + public void scheduleTimeoutAction(Configuration config, Runnable action) { + config.getHandler().postDelayed(action, config.mTimeout); } /** * Ends a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. * @return boolean true if the tracker is ended successfully, false otherwise. */ - public boolean end(@CujType int cujType) { + public boolean end(@Cuj.CujType int cujType) { postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> { EventLogTags.writeJankCujEventsEndRequest( cujType, unixNanos, elapsedNanos, realtimeNanos); }); - FrameTracker tracker = getTracker(cujType); + RunningTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. - if (tracker == null) return false; + if (tracker == null) { + return false; + } try { final TrackerResult result = new TrackerResult(); - final boolean success = tracker.getHandler().runWithScissors( - () -> result.mResult = endInternal(cujType), EXECUTOR_TASK_TIMEOUT); + final boolean success = tracker.mConfig.getHandler().runWithScissors( + () -> result.mResult = endInternal(tracker), EXECUTOR_TASK_TIMEOUT); if (!success) { - Log.d(TAG, "end failed due to timeout, CUJ=" + getNameOfCuj(cujType)); + Log.d(TAG, "end failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType)); return false; } return result.mResult; @@ -759,15 +407,11 @@ public class InteractionJankMonitor { } @UiThread - private boolean endInternal(@CujType int cujType) { - // remove the timeout action first. - removeTimeout(cujType); - FrameTracker tracker = getTracker(cujType); - if (tracker == null) return false; - // if the end call doesn't return true, another thread is handling end of the cuj. - if (tracker.end(REASON_END_NORMAL)) { - removeTracker(cujType, REASON_END_NORMAL); + private boolean endInternal(RunningTracker tracker) { + if (removeTrackerIfCurrent(tracker, REASON_END_NORMAL)) { + return false; } + tracker.mTracker.end(REASON_END_NORMAL); return true; } @@ -776,7 +420,7 @@ public class InteractionJankMonitor { * * @return boolean true if the tracker is cancelled successfully, false otherwise. */ - public boolean cancel(@CujType int cujType) { + public boolean cancel(@Cuj.CujType int cujType) { postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> { EventLogTags.writeJankCujEventsCancelRequest( cujType, unixNanos, elapsedNanos, realtimeNanos); @@ -790,16 +434,18 @@ public class InteractionJankMonitor { * @return boolean true if the tracker is cancelled successfully, false otherwise. */ @VisibleForTesting - public boolean cancel(@CujType int cujType, @Reasons int reason) { - FrameTracker tracker = getTracker(cujType); + public boolean cancel(@Cuj.CujType int cujType, @Reasons int reason) { + RunningTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. - if (tracker == null) return false; + if (tracker == null) { + return false; + } try { final TrackerResult result = new TrackerResult(); - final boolean success = tracker.getHandler().runWithScissors( - () -> result.mResult = cancelInternal(cujType, reason), EXECUTOR_TASK_TIMEOUT); + final boolean success = tracker.mConfig.getHandler().runWithScissors( + () -> result.mResult = cancelInternal(tracker, reason), EXECUTOR_TASK_TIMEOUT); if (!success) { - Log.d(TAG, "cancel failed due to timeout, CUJ=" + getNameOfCuj(cujType)); + Log.d(TAG, "cancel failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType)); return false; } return result.mResult; @@ -810,71 +456,86 @@ public class InteractionJankMonitor { } @UiThread - private boolean cancelInternal(@CujType int cujType, @Reasons int reason) { - // remove the timeout action first. - removeTimeout(cujType); - FrameTracker tracker = getTracker(cujType); - if (tracker == null) return false; - // if the cancel call doesn't return true, another thread is handling cancel of the cuj. - if (tracker.cancel(reason)) { - removeTracker(cujType, reason); + private boolean cancelInternal(RunningTracker tracker, @Reasons int reason) { + if (removeTrackerIfCurrent(tracker, reason)) { + return false; } + tracker.mTracker.cancel(reason); return true; } @UiThread - private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) { + private RunningTracker putTrackerIfNoCurrent( + @Cuj.CujType int cuj, Supplier<RunningTracker> supplier) { synchronized (mLock) { + if (mRunningTrackers.contains(cuj)) { + return null; + } + + RunningTracker tracker = supplier.get(); + if (tracker == null) { + return null; + } + mRunningTrackers.put(cuj, tracker); if (mDebugOverlay != null) { mDebugOverlay.onTrackerAdded(cuj, tracker); } - if (DEBUG) { - Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj) - + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers)); - } + + return tracker; } } - private FrameTracker getTracker(@CujType int cuj) { + private RunningTracker getTracker(@Cuj.CujType int cuj) { synchronized (mLock) { return mRunningTrackers.get(cuj); } } + /** + * @return {@code true} if another tracker is current + */ + @UiThread + private boolean removeTrackerIfCurrent(RunningTracker tracker, int reason) { + return removeTrackerIfCurrent(tracker.mConfig.mCujType, tracker.mTracker, reason); + } + + /** + * @return {@code true} if another tracker is current + */ @UiThread - private void removeTracker(@CujType int cuj, int reason) { + private boolean removeTrackerIfCurrent(@Cuj.CujType int cuj, FrameTracker tracker, int reason) { synchronized (mLock) { + RunningTracker running = mRunningTrackers.get(cuj); + if (running == null || running.mTracker != tracker) { + return true; + } + + running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction); mRunningTrackers.remove(cuj); if (mDebugOverlay != null) { mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers); } - if (DEBUG) { - Log.d(TAG, "Removed tracker for " + getNameOfCuj(cuj) - + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers)); - } + return false; } } @WorkerThread - private void updateProperties(DeviceConfig.Properties properties) { + @VisibleForTesting + public void updateProperties(DeviceConfig.Properties properties) { for (String property : properties.getKeyset()) { switch (property) { - case SETTINGS_SAMPLING_INTERVAL_KEY: - mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL); - break; - case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY: - mTraceThresholdMissedFrames = - properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES); - break; - case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY: - mTraceThresholdFrameTimeMillis = - properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS); - break; - case SETTINGS_ENABLED_KEY: - mEnabled = properties.getBoolean(property, DEFAULT_ENABLED); - break; - case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY: + case SETTINGS_SAMPLING_INTERVAL_KEY -> + mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL); + case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY -> + mTraceThresholdMissedFrames = + properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES); + case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY -> + mTraceThresholdFrameTimeMillis = + properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS); + case SETTINGS_ENABLED_KEY -> + mEnabled = properties.getBoolean(property, DEFAULT_ENABLED); + case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> { // Never allow the debug overlay to be used on user builds boolean debugOverlayEnabled = Build.IS_DEBUGGABLE && properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED); @@ -885,49 +546,35 @@ public class InteractionJankMonitor { mDebugOverlay.dispose(); mDebugOverlay = null; } - break; - default: - if (DEBUG) { - Log.d(TAG, "Got a change event for an unknown property: " - + property + " => " + properties.getString(property, "")); - } + } + default -> Log.w(TAG, "Got a change event for an unknown property: " + + property + " => " + properties.getString(property, "")); } } } - @VisibleForTesting - public DeviceConfig.OnPropertiesChangedListener getPropertiesChangedListener() { - return mPropertiesChangedListener; - } - - /** - * Triggers the perfetto daemon to collect and upload data. - */ - @VisibleForTesting - public void trigger(Session session) { - mWorker.getThreadHandler().post( - () -> PerfettoTrigger.trigger(session.getPerfettoTrigger())); - } - /** * A helper method to translate interaction type to CUJ name. * * @param interactionType the interaction type defined in AtomsProto.java * @return the name of the interaction type + * @deprecated use {@link Cuj#getNameOfInteraction(int)} */ + @Deprecated public static String getNameOfInteraction(int interactionType) { - // There is an offset amount of 1 between cujType and interactionType. - return getNameOfCuj(getCujTypeFromInteraction(interactionType)); + return Cuj.getNameOfInteraction(interactionType); } /** - * A helper method to translate interaction type to CUJ type. + * A helper method to translate CUJ type to CUJ name. * - * @param interactionType the interaction type defined in AtomsProto.java - * @return the integer in {@link CujType} + * @param cujType the cuj type defined in this file + * @return the name of the cuj type + * @deprecated use {@link Cuj#getNameOfCuj(int)} */ - private static int getCujTypeFromInteraction(int interactionType) { - return interactionType - 1; + @Deprecated + public static String getNameOfCuj(int cujType) { + return Cuj.getNameOfCuj(cujType); } /** @@ -943,195 +590,14 @@ public class InteractionJankMonitor { mDebugYOffset = yOffset; } - /** - * A helper method for getting a string representation of all running CUJs. For example, - * "(LOCKSCREEN_TRANSITION_FROM_AOD, IME_INSETS_ANIMATION)" - */ - private static String listNamesOfCujs(SparseArray<FrameTracker> trackers) { - if (!DEBUG) { - return null; - } - StringBuilder sb = new StringBuilder(); - sb.append('('); - for (int i = 0; i < trackers.size(); i++) { - sb.append(getNameOfCuj(trackers.keyAt(i))); - if (i < trackers.size() - 1) { - sb.append(", "); - } - } - sb.append(')'); - return sb.toString(); - } + private void postEventLogToWorkerThread(TimeFunction logFunction) { + final Instant now = Instant.now(); + final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS) + + now.getNano(); + final long elapsedNanos = SystemClock.elapsedRealtimeNanos(); + final long realtimeNanos = SystemClock.uptimeNanos(); - /** - * A helper method to translate CUJ type to CUJ name. - * - * @param cujType the cuj type defined in this file - * @return the name of the cuj type - */ - public static String getNameOfCuj(int cujType) { - // Please note: - // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME. - // 2. The returned string should be the same with the name defined in atoms.proto. - switch (cujType) { - case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE: - return "NOTIFICATION_SHADE_EXPAND_COLLAPSE"; - case CUJ_NOTIFICATION_SHADE_SCROLL_FLING: - return "NOTIFICATION_SHADE_SCROLL_FLING"; - case CUJ_NOTIFICATION_SHADE_ROW_EXPAND: - return "NOTIFICATION_SHADE_ROW_EXPAND"; - case CUJ_NOTIFICATION_SHADE_ROW_SWIPE: - return "NOTIFICATION_SHADE_ROW_SWIPE"; - case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE: - return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE"; - case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE: - return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE"; - case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS: - return "LAUNCHER_APP_LAUNCH_FROM_RECENTS"; - case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON: - return "LAUNCHER_APP_LAUNCH_FROM_ICON"; - case CUJ_LAUNCHER_APP_CLOSE_TO_HOME: - return "LAUNCHER_APP_CLOSE_TO_HOME"; - case CUJ_LAUNCHER_APP_CLOSE_TO_PIP: - return "LAUNCHER_APP_CLOSE_TO_PIP"; - case CUJ_LAUNCHER_QUICK_SWITCH: - return "LAUNCHER_QUICK_SWITCH"; - case CUJ_NOTIFICATION_HEADS_UP_APPEAR: - return "NOTIFICATION_HEADS_UP_APPEAR"; - case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR: - return "NOTIFICATION_HEADS_UP_DISAPPEAR"; - case CUJ_NOTIFICATION_ADD: - return "NOTIFICATION_ADD"; - case CUJ_NOTIFICATION_REMOVE: - return "NOTIFICATION_REMOVE"; - case CUJ_NOTIFICATION_APP_START: - return "NOTIFICATION_APP_START"; - case CUJ_LOCKSCREEN_PASSWORD_APPEAR: - return "LOCKSCREEN_PASSWORD_APPEAR"; - case CUJ_LOCKSCREEN_PATTERN_APPEAR: - return "LOCKSCREEN_PATTERN_APPEAR"; - case CUJ_LOCKSCREEN_PIN_APPEAR: - return "LOCKSCREEN_PIN_APPEAR"; - case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR: - return "LOCKSCREEN_PASSWORD_DISAPPEAR"; - case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR: - return "LOCKSCREEN_PATTERN_DISAPPEAR"; - case CUJ_LOCKSCREEN_PIN_DISAPPEAR: - return "LOCKSCREEN_PIN_DISAPPEAR"; - case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD: - return "LOCKSCREEN_TRANSITION_FROM_AOD"; - case CUJ_LOCKSCREEN_TRANSITION_TO_AOD: - return "LOCKSCREEN_TRANSITION_TO_AOD"; - case CUJ_LAUNCHER_OPEN_ALL_APPS : - return "LAUNCHER_OPEN_ALL_APPS"; - case CUJ_LAUNCHER_ALL_APPS_SCROLL: - return "LAUNCHER_ALL_APPS_SCROLL"; - case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET: - return "LAUNCHER_APP_LAUNCH_FROM_WIDGET"; - case CUJ_SETTINGS_PAGE_SCROLL: - return "SETTINGS_PAGE_SCROLL"; - case CUJ_LOCKSCREEN_UNLOCK_ANIMATION: - return "LOCKSCREEN_UNLOCK_ANIMATION"; - case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON: - return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON"; - case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER: - return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER"; - case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE: - return "SHADE_APP_LAUNCH_FROM_QS_TILE"; - case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON: - return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON"; - case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP: - return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP"; - case CUJ_PIP_TRANSITION: - return "PIP_TRANSITION"; - case CUJ_WALLPAPER_TRANSITION: - return "WALLPAPER_TRANSITION"; - case CUJ_USER_SWITCH: - return "USER_SWITCH"; - case CUJ_SPLASHSCREEN_AVD: - return "SPLASHSCREEN_AVD"; - case CUJ_SPLASHSCREEN_EXIT_ANIM: - return "SPLASHSCREEN_EXIT_ANIM"; - case CUJ_SCREEN_OFF: - return "SCREEN_OFF"; - case CUJ_SCREEN_OFF_SHOW_AOD: - return "SCREEN_OFF_SHOW_AOD"; - case CUJ_ONE_HANDED_ENTER_TRANSITION: - return "ONE_HANDED_ENTER_TRANSITION"; - case CUJ_ONE_HANDED_EXIT_TRANSITION: - return "ONE_HANDED_EXIT_TRANSITION"; - case CUJ_UNFOLD_ANIM: - return "UNFOLD_ANIM"; - case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS: - return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS"; - case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS: - return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS"; - case CUJ_SUW_LOADING_TO_NEXT_FLOW: - return "SUW_LOADING_TO_NEXT_FLOW"; - case CUJ_SUW_LOADING_SCREEN_FOR_STATUS: - return "SUW_LOADING_SCREEN_FOR_STATUS"; - case CUJ_SPLIT_SCREEN_ENTER: - return "SPLIT_SCREEN_ENTER"; - case CUJ_SPLIT_SCREEN_EXIT: - return "SPLIT_SCREEN_EXIT"; - case CUJ_LOCKSCREEN_LAUNCH_CAMERA: - return "LOCKSCREEN_LAUNCH_CAMERA"; - case CUJ_SPLIT_SCREEN_RESIZE: - return "SPLIT_SCREEN_RESIZE"; - case CUJ_SETTINGS_SLIDER: - return "SETTINGS_SLIDER"; - case CUJ_TAKE_SCREENSHOT: - return "TAKE_SCREENSHOT"; - case CUJ_VOLUME_CONTROL: - return "VOLUME_CONTROL"; - case CUJ_BIOMETRIC_PROMPT_TRANSITION: - return "BIOMETRIC_PROMPT_TRANSITION"; - case CUJ_SETTINGS_TOGGLE: - return "SETTINGS_TOGGLE"; - case CUJ_SHADE_DIALOG_OPEN: - return "SHADE_DIALOG_OPEN"; - case CUJ_USER_DIALOG_OPEN: - return "USER_DIALOG_OPEN"; - case CUJ_TASKBAR_EXPAND: - return "TASKBAR_EXPAND"; - case CUJ_TASKBAR_COLLAPSE: - return "TASKBAR_COLLAPSE"; - case CUJ_SHADE_CLEAR_ALL: - return "SHADE_CLEAR_ALL"; - case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION: - return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION"; - case CUJ_LOCKSCREEN_OCCLUSION: - return "LOCKSCREEN_OCCLUSION"; - case CUJ_RECENTS_SCROLLING: - return "RECENTS_SCROLLING"; - case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS: - return "LAUNCHER_APP_SWIPE_TO_RECENTS"; - case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE: - return "LAUNCHER_CLOSE_ALL_APPS_SWIPE"; - case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME: - return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME"; - case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION: - return "LOCKSCREEN_CLOCK_MOVE_ANIMATION"; - case CUJ_LAUNCHER_OPEN_SEARCH_RESULT: - return "LAUNCHER_OPEN_SEARCH_RESULT"; - case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK: - return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK"; - case CUJ_IME_INSETS_SHOW_ANIMATION: - return "IME_INSETS_SHOW_ANIMATION"; - case CUJ_IME_INSETS_HIDE_ANIMATION: - return "IME_INSETS_HIDE_ANIMATION"; - case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER: - return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER"; - case CUJ_LAUNCHER_UNFOLD_ANIM: - return "LAUNCHER_UNFOLD_ANIM"; - case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY: - return "PREDICTIVE_BACK_CROSS_ACTIVITY"; - case CUJ_PREDICTIVE_BACK_CROSS_TASK: - return "PREDICTIVE_BACK_CROSS_TASK"; - case CUJ_PREDICTIVE_BACK_HOME: - return "PREDICTIVE_BACK_HOME"; - } - return "UNKNOWN"; + mWorker.post(() -> logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos)); } private static class TrackerResult { @@ -1147,9 +613,10 @@ public class InteractionJankMonitor { private final Context mContext; private final long mTimeout; private final String mTag; + private final String mSessionName; private final boolean mSurfaceOnly; private final SurfaceControl mSurfaceControl; - private final @CujType int mCujType; + private final @Cuj.CujType int mCujType; private final boolean mDeferMonitor; private final Handler mHandler; @@ -1167,17 +634,17 @@ public class InteractionJankMonitor { private String mAttrTag = ""; private boolean mAttrSurfaceOnly; private SurfaceControl mAttrSurfaceControl; - private @CujType int mAttrCujType; + private final @Cuj.CujType int mAttrCujType; private boolean mAttrDeferMonitor = true; /** * Creates a builder which instruments only surface. - * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}. + * @param cuj The enum defined in {@link Cuj.CujType}. * @param context context * @param surfaceControl surface control * @return builder */ - public static Builder withSurface(@CujType int cuj, @NonNull Context context, + public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context, @NonNull SurfaceControl surfaceControl) { return new Builder(cuj) .setContext(context) @@ -1187,16 +654,17 @@ public class InteractionJankMonitor { /** * Creates a builder which instruments both surface and view. - * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}. + * @param cuj The enum defined in {@link Cuj.CujType}. * @param view view * @return builder */ - public static Builder withView(@CujType int cuj, @NonNull View view) { - return new Builder(cuj).setView(view) + public static Builder withView(@Cuj.CujType int cuj, @NonNull View view) { + return new Builder(cuj) + .setView(view) .setContext(view.getContext()); } - private Builder(@CujType int cuj) { + private Builder(@Cuj.CujType int cuj) { mAttrCujType = cuj; } @@ -1281,11 +749,12 @@ public class InteractionJankMonitor { } } - private Configuration(@CujType int cuj, View view, String tag, long timeout, + private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout, boolean surfaceOnly, Context context, SurfaceControl surfaceControl, boolean deferMonitor) { mCujType = cuj; mTag = tag; + mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag); mTimeout = timeout; mView = view; mSurfaceOnly = surfaceOnly; @@ -1298,6 +767,23 @@ public class InteractionJankMonitor { mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler(); } + @VisibleForTesting + public static String generateSessionName( + @NonNull String cujName, @NonNull String cujPostfix) { + final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix); + if (hasPostfix) { + final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length(); + if (cujPostfix.length() > remaining) { + cujPostfix = cujPostfix.substring(0, remaining - 3).concat("..."); + } + } + // The max length of the whole string should be: + // 105 with postfix, 83 without postfix + return hasPostfix + ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix) + : TextUtils.formatSimple("J<%s>", cujName); + } + private void validate() { boolean shouldThrow = false; final StringBuilder msg = new StringBuilder(); @@ -1360,10 +846,10 @@ public class InteractionJankMonitor { return mSurfaceControl; } - @VisibleForTesting /** * @return a view which is attached to the view tree. */ + @VisibleForTesting public View getView() { return mView; } @@ -1375,7 +861,7 @@ public class InteractionJankMonitor { return mDeferMonitor; } - @VisibleForTesting + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public Handler getHandler() { return mHandler; } @@ -1387,79 +873,27 @@ public class InteractionJankMonitor { public int getDisplayId() { return (mSurfaceOnly ? mContext : mView.getContext()).getDisplayId(); } - } - - /** - * A class to represent a session. - */ - public static class Session { - @CujType - private final int mCujType; - private final long mTimeStamp; - @Reasons - private int mReason = REASON_END_UNKNOWN; - private final String mName; - - public Session(@CujType int cujType, @NonNull String postfix) { - mCujType = cujType; - mTimeStamp = System.nanoTime(); - mName = generateSessionName(getNameOfCuj(cujType), postfix); - } - - private String generateSessionName(@NonNull String cujName, @NonNull String cujPostfix) { - final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix); - // We assert that the cujName shouldn't exceed MAX_LENGTH_OF_CUJ_NAME. - if (cujName.length() > MAX_LENGTH_OF_CUJ_NAME) { - throw new IllegalArgumentException(TextUtils.formatSimple( - "The length of cuj name <%s> exceeds %d", cujName, MAX_LENGTH_OF_CUJ_NAME)); - } - if (hasPostfix) { - final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length(); - if (cujPostfix.length() > remaining) { - cujPostfix = cujPostfix.substring(0, remaining - 3).concat("..."); - } - } - // The max length of the whole string should be: - // 105 with postfix, 83 without postfix - return hasPostfix - ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix) - : TextUtils.formatSimple("J<%s>", cujName); - } - @CujType - public int getCuj() { - return mCujType; + public String getSessionName() { + return mSessionName; } public int getStatsdInteractionType() { - return CUJ_TO_STATSD_INTERACTION_TYPE[mCujType]; + return Cuj.getStatsdInteractionType(mCujType); } /** Describes whether the measurement from this session should be written to statsd. */ public boolean logToStatsd() { - return getStatsdInteractionType() != NO_STATSD_LOGGING; + return Cuj.logToStatsd(mCujType); } public String getPerfettoTrigger() { - return String.format(Locale.US, "com.android.telemetry.interaction-jank-monitor-%d", - mCujType); - } - - public String getName() { - return mName; - } - - public long getTimeStamp() { - return mTimeStamp; - } - - public void setReason(@Reasons int reason) { - mReason = reason; + return TextUtils.formatSimple( + "com.android.telemetry.interaction-jank-monitor-%d", mCujType); } - @Reasons - public int getReason() { - return mReason; + public @Cuj.CujType int getCujType() { + return mCujType; } } @@ -1468,15 +902,15 @@ public class InteractionJankMonitor { void invoke(long unixNanos, long elapsedNanos, long realtimeNanos); } - private void postEventLogToWorkerThread(TimeFunction logFunction) { - final Instant now = Instant.now(); - final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS) - + now.getNano(); - final long elapsedNanos = SystemClock.elapsedRealtimeNanos(); - final long realtimeNanos = SystemClock.uptimeNanos(); + static class RunningTracker { + public final Configuration mConfig; + public final FrameTracker mTracker; + public final Runnable mTimeoutAction; - mWorker.getThreadHandler().post(() -> { - logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos); - }); + RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction) { + this.mConfig = config; + this.mTracker = tracker; + this.mTimeoutAction = timeoutAction; + } } } diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java index ef7944c21ad2..f3f16a0c662d 100644 --- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java +++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java @@ -34,7 +34,6 @@ import android.view.WindowCallbacks; import com.android.internal.annotations.GuardedBy; import com.android.internal.jank.FrameTracker.Reasons; -import com.android.internal.jank.InteractionJankMonitor.CujType; /** * An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window @@ -94,14 +93,14 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { } @UiThread - private boolean attachViewRootIfNeeded(FrameTracker tracker) { - FrameTracker.ViewRootWrapper viewRoot = tracker.getViewRoot(); + private boolean attachViewRootIfNeeded(InteractionJankMonitor.RunningTracker tracker) { + FrameTracker.ViewRootWrapper viewRoot = tracker.mTracker.getViewRoot(); if (mViewRoot == null && viewRoot != null) { // Add a trace marker so we can identify traces that were captured while the debug // overlay was enabled. Traces that use the debug overlay should NOT be used for // performance analysis. Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0); - mHandler = tracker.getHandler(); + mHandler = tracker.mConfig.getHandler(); mViewRoot = viewRoot; mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this), InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT); @@ -111,11 +110,12 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { return false; } + @GuardedBy("mLock") private float getWidthOfLongestCujName(int cujFontSize) { mDebugPaint.setTextSize(cujFontSize); float maxLength = 0; for (int i = 0; i < mRunningCujs.size(); i++) { - String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i)); + String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i)); float textLength = mDebugPaint.measureText(cujName); if (textLength > maxLength) { maxLength = textLength; @@ -149,8 +149,8 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { } @UiThread - void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason, - SparseArray<FrameTracker> runningTrackers) { + void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason, + SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) { synchronized (mLock) { mRunningCujs.put(removedCuj, reason); // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended @@ -164,7 +164,7 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { // trackers for (int i = 0; i < runningTrackers.size(); i++) { if (mViewRoot.equals( - runningTrackers.valueAt(i).getViewRoot())) { + runningTrackers.valueAt(i).mTracker.getViewRoot())) { needsNewViewRoot = false; break; } @@ -185,7 +185,7 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { } @UiThread - void onTrackerAdded(@CujType int addedCuj, FrameTracker tracker) { + void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) { synchronized (mLock) { // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ // is still running @@ -230,41 +230,44 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { int cujFontSize = dipToPx(18); final float cujNameTextHeight = getTextHeight(cujFontSize); final float packageNameTextHeight = getTextHeight(packageNameFontSize); - float maxLength = getWidthOfLongestCujName(cujFontSize); - final int dx = (int) ((w - maxLength) / 2f); - canvas.translate(dx, dy); - // Draw background rectangle for displaying the text showing the CUJ name - mDebugPaint.setColor(mBgColor); - canvas.drawRect( - -padding * 2, // more padding on top so we can draw the package name - -padding, - padding * 2 + maxLength, - padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(), - mDebugPaint); - mDebugPaint.setTextSize(packageNameFontSize); - mDebugPaint.setColor(Color.BLACK); - mDebugPaint.setStrikeThruText(false); - canvas.translate(0, packageNameTextHeight); - canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint); - mDebugPaint.setTextSize(cujFontSize); - // Draw text for CUJ names - for (int i = 0; i < mRunningCujs.size(); i++) { - int status = mRunningCujs.valueAt(i); - if (status == REASON_STILL_RUNNING) { - mDebugPaint.setColor(Color.BLACK); - mDebugPaint.setStrikeThruText(false); - } else if (status == REASON_END_NORMAL) { - mDebugPaint.setColor(Color.GRAY); - mDebugPaint.setStrikeThruText(false); - } else { - // Cancelled, or otherwise ended for a bad reason - mDebugPaint.setColor(Color.RED); - mDebugPaint.setStrikeThruText(true); + synchronized (mLock) { + float maxLength = getWidthOfLongestCujName(cujFontSize); + + final int dx = (int) ((w - maxLength) / 2f); + canvas.translate(dx, dy); + // Draw background rectangle for displaying the text showing the CUJ name + mDebugPaint.setColor(mBgColor); + canvas.drawRect( + -padding * 2, // more padding on top so we can draw the package name + -padding, + padding * 2 + maxLength, + padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(), + mDebugPaint); + mDebugPaint.setTextSize(packageNameFontSize); + mDebugPaint.setColor(Color.BLACK); + mDebugPaint.setStrikeThruText(false); + canvas.translate(0, packageNameTextHeight); + canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint); + mDebugPaint.setTextSize(cujFontSize); + // Draw text for CUJ names + for (int i = 0; i < mRunningCujs.size(); i++) { + int status = mRunningCujs.valueAt(i); + if (status == REASON_STILL_RUNNING) { + mDebugPaint.setColor(Color.BLACK); + mDebugPaint.setStrikeThruText(false); + } else if (status == REASON_END_NORMAL) { + mDebugPaint.setColor(Color.GRAY); + mDebugPaint.setStrikeThruText(false); + } else { + // Cancelled, or otherwise ended for a bad reason + mDebugPaint.setColor(Color.RED); + mDebugPaint.setStrikeThruText(true); + } + String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i)); + canvas.translate(0, cujNameTextHeight); + canvas.drawText(cujName, 0, 0, mDebugPaint); } - String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i)); - canvas.translate(0, cujNameTextHeight); - canvas.drawText(cujName, 0, 0, mDebugPaint); } } } 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/BinderCallHeavyHitterWatcher.java b/core/java/com/android/internal/os/BinderCallHeavyHitterWatcher.java index 7701761baac8..00043cfcd56b 100644 --- a/core/java/com/android/internal/os/BinderCallHeavyHitterWatcher.java +++ b/core/java/com/android/internal/os/BinderCallHeavyHitterWatcher.java @@ -33,6 +33,7 @@ import java.util.List; * A watcher which makes stats on the incoming binder transaction, if the amount of some type of * transactions exceeds the threshold, the listener will be notified. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class BinderCallHeavyHitterWatcher { private static final String TAG = "BinderCallHeavyHitterWatcher"; diff --git a/core/java/com/android/internal/os/BinderDeathDispatcher.java b/core/java/com/android/internal/os/BinderDeathDispatcher.java index 8ca6241e63c6..e7abe2a83ad8 100644 --- a/core/java/com/android/internal/os/BinderDeathDispatcher.java +++ b/core/java/com/android/internal/os/BinderDeathDispatcher.java @@ -36,6 +36,7 @@ import com.android.internal.annotations.VisibleForTesting; * * test with: atest FrameworksCoreTests:BinderDeathDispatcherTest */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BinderDeathDispatcher<T extends IInterface> { private static final String TAG = "BinderDeathDispatcher"; diff --git a/core/java/com/android/internal/os/BinderLatencyBuckets.java b/core/java/com/android/internal/os/BinderLatencyBuckets.java index d7d2d6a8f9dd..5679bc778eee 100644 --- a/core/java/com/android/internal/os/BinderLatencyBuckets.java +++ b/core/java/com/android/internal/os/BinderLatencyBuckets.java @@ -26,6 +26,7 @@ import java.util.Arrays; * Generates the bucket thresholds (with a custom logarithmic scale) for a histogram to store * latency samples in. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BinderLatencyBuckets { private static final String TAG = "BinderLatencyBuckets"; private final int[] mBuckets; diff --git a/core/java/com/android/internal/os/BinderfsStatsReader.java b/core/java/com/android/internal/os/BinderfsStatsReader.java index 9cc4a35b5c65..66f91e192a88 100644 --- a/core/java/com/android/internal/os/BinderfsStatsReader.java +++ b/core/java/com/android/internal/os/BinderfsStatsReader.java @@ -43,6 +43,7 @@ import java.util.function.Predicate; * free async space 520192 * ... */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class BinderfsStatsReader { private final String mPath; diff --git a/core/java/com/android/internal/os/CachedDeviceState.java b/core/java/com/android/internal/os/CachedDeviceState.java index 334cca317c30..ac92f866eb7e 100644 --- a/core/java/com/android/internal/os/CachedDeviceState.java +++ b/core/java/com/android/internal/os/CachedDeviceState.java @@ -30,6 +30,7 @@ import java.util.ArrayList; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class CachedDeviceState { private volatile boolean mScreenInteractive; private volatile boolean mCharging; diff --git a/core/java/com/android/internal/os/Clock.java b/core/java/com/android/internal/os/Clock.java index 45007c48777f..c2403d1e9f89 100644 --- a/core/java/com/android/internal/os/Clock.java +++ b/core/java/com/android/internal/os/Clock.java @@ -21,6 +21,7 @@ import android.os.SystemClock; /** * A wrapper for SystemClock, intended for mocking in unit tests. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class Clock { /** Elapsed Realtime, see SystemClock.elapsedRealtime() */ public long elapsedRealtime() { diff --git a/core/java/com/android/internal/os/CpuScalingPolicies.java b/core/java/com/android/internal/os/CpuScalingPolicies.java index 6dbe8ab5f567..f61cf97c3277 100644 --- a/core/java/com/android/internal/os/CpuScalingPolicies.java +++ b/core/java/com/android/internal/os/CpuScalingPolicies.java @@ -27,6 +27,7 @@ import java.util.Arrays; * CPU scaling policies: the policy IDs and corresponding supported scaling for those * policies. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class CpuScalingPolicies { private final SparseArray<int[]> mCpusByPolicy; private final SparseArray<int[]> mFreqsByPolicy; diff --git a/core/java/com/android/internal/os/CpuScalingPolicyReader.java b/core/java/com/android/internal/os/CpuScalingPolicyReader.java index c96089a5c9c9..0d272fdd1578 100644 --- a/core/java/com/android/internal/os/CpuScalingPolicyReader.java +++ b/core/java/com/android/internal/os/CpuScalingPolicyReader.java @@ -40,6 +40,7 @@ import java.util.regex.Pattern; * href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html * #policy-interface-in-sysfs">Policy Interface in sysfs</a> */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class CpuScalingPolicyReader { private static final String TAG = "CpuScalingPolicyReader"; private static final String CPUFREQ_DIR = "/sys/devices/system/cpu/cpufreq"; diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java index 0843741d237c..5b6d1b63a42f 100644 --- a/core/java/com/android/internal/os/KernelCpuThreadReader.java +++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java @@ -244,7 +244,8 @@ public class KernelCpuThreadReader { } /** Set the UID predicate for {@link #getProcessCpuUsage} */ - void setUidPredicate(Predicate<Integer> uidPredicate) { + @VisibleForTesting + public void setUidPredicate(Predicate<Integer> uidPredicate) { mUidPredicate = uidPredicate; } diff --git a/core/java/com/android/internal/os/LoggingPrintStream.java b/core/java/com/android/internal/os/LoggingPrintStream.java index d27874cd3be2..4bf92bb5e256 100644 --- a/core/java/com/android/internal/os/LoggingPrintStream.java +++ b/core/java/com/android/internal/os/LoggingPrintStream.java @@ -36,6 +36,7 @@ import com.android.internal.annotations.VisibleForTesting; * {@hide} */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class LoggingPrintStream extends PrintStream { private final StringBuilder builder = new StringBuilder(); diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 1f44b338f3f7..ed943cb2385d 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -55,15 +55,20 @@ import java.util.concurrent.atomic.AtomicReference; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( + "com.android.hoststubgen.nativesubstitution.LongArrayMultiStateCounter_host") public final class LongArrayMultiStateCounter implements Parcelable { /** * Container for a native equivalent of a long[]. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass + @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( + "com.android.hoststubgen.nativesubstitution" + + ".LongArrayMultiStateCounter_host$LongArrayContainer_host") public static class LongArrayContainer { - private static final NativeAllocationRegistry sRegistry = - NativeAllocationRegistry.createMalloced( - LongArrayContainer.class.getClassLoader(), native_getReleaseFunc()); + private static NativeAllocationRegistry sRegistry; // Visible to other objects in this package so that it can be passed to @CriticalNative // methods. @@ -73,9 +78,26 @@ public final class LongArrayMultiStateCounter implements Parcelable { public LongArrayContainer(int length) { mLength = length; mNativeObject = native_init(length); + registerNativeAllocation(); + } + + @android.ravenwood.annotation.RavenwoodReplace + private void registerNativeAllocation() { + if (sRegistry == null) { + synchronized (LongArrayMultiStateCounter.class) { + if (sRegistry == null) { + sRegistry = NativeAllocationRegistry.createMalloced( + LongArrayContainer.class.getClassLoader(), native_getReleaseFunc()); + } + } + } sRegistry.registerNativeAllocation(this, mNativeObject); } + private void registerNativeAllocation$ravenwood() { + // No-op under ravenwood + } + /** * Copies the supplied values into the underlying native array. */ @@ -124,19 +146,17 @@ public final class LongArrayMultiStateCounter implements Parcelable { private static native long native_getReleaseFunc(); @FastNative - private native void native_setValues(long nativeObject, long[] array); + private static native void native_setValues(long nativeObject, long[] array); @FastNative - private native void native_getValues(long nativeObject, long[] array); + private static native void native_getValues(long nativeObject, long[] array); @FastNative - private native boolean native_combineValues(long nativeObject, long[] array, + private static native boolean native_combineValues(long nativeObject, long[] array, int[] indexMap); } - private static final NativeAllocationRegistry sRegistry = - NativeAllocationRegistry.createMalloced( - LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc()); + private static volatile NativeAllocationRegistry sRegistry; private static final AtomicReference<LongArrayContainer> sTmpArrayContainer = new AtomicReference<>(); @@ -152,12 +172,30 @@ public final class LongArrayMultiStateCounter implements Parcelable { mStateCount = stateCount; mLength = arrayLength; mNativeObject = native_init(stateCount, arrayLength); + registerNativeAllocation(); + } + + @android.ravenwood.annotation.RavenwoodReplace + private void registerNativeAllocation() { + if (sRegistry == null) { + synchronized (LongArrayMultiStateCounter.class) { + if (sRegistry == null) { + sRegistry = NativeAllocationRegistry.createMalloced( + LongArrayMultiStateCounter.class.getClassLoader(), + native_getReleaseFunc()); + } + } + } sRegistry.registerNativeAllocation(this, mNativeObject); } + private void registerNativeAllocation$ravenwood() { + // No-op under ravenwood + } + private LongArrayMultiStateCounter(Parcel in) { mNativeObject = native_initFromParcel(in); - sRegistry.registerNativeAllocation(this, mNativeObject); + registerNativeAllocation(); mStateCount = native_getStateCount(mNativeObject); mLength = native_getArrayLength(mNativeObject); @@ -361,10 +399,10 @@ public final class LongArrayMultiStateCounter implements Parcelable { long longArrayContainerNativeObject, int state); @FastNative - private native String native_toString(long nativeObject); + private static native String native_toString(long nativeObject); @FastNative - private native void native_writeToParcel(long nativeObject, Parcel dest, int flags); + private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags); @FastNative private static native long native_initFromParcel(Parcel parcel); diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java index 0645eb7f0835..bbcea8af2b87 100644 --- a/core/java/com/android/internal/os/LooperStats.java +++ b/core/java/com/android/internal/os/LooperStats.java @@ -36,6 +36,7 @@ import java.util.concurrent.ThreadLocalRandom; * * @hide Only for use within the system server. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class LooperStats implements Looper.Observer { public static final String DEBUG_ENTRY_PREFIX = "__DEBUG_"; private static final int SESSION_POOL_SIZE = 50; diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java index 661628a8c148..c3bcfa6b2cc2 100644 --- a/core/java/com/android/internal/os/MonotonicClock.java +++ b/core/java/com/android/internal/os/MonotonicClock.java @@ -40,6 +40,7 @@ import java.nio.charset.StandardCharsets; * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset * on reboot, but keeps going. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class MonotonicClock { private static final String TAG = "MonotonicClock"; 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/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java index 9ddb8c75f5c8..6b85e08877ae 100644 --- a/core/java/com/android/internal/os/ProcLocksReader.java +++ b/core/java/com/android/internal/os/ProcLocksReader.java @@ -34,6 +34,7 @@ import java.io.IOException; * 3: POSIX ADVISORY READ 3888 fd:09:13992 128 128 * 4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335 */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ProcLocksReader { private final String mPath; private ProcFileReader mReader = null; diff --git a/core/java/com/android/internal/os/ProcStatsUtil.java b/core/java/com/android/internal/os/ProcStatsUtil.java index 0002447fda2a..b67190bc7e88 100644 --- a/core/java/com/android/internal/os/ProcStatsUtil.java +++ b/core/java/com/android/internal/os/ProcStatsUtil.java @@ -30,6 +30,7 @@ import java.io.IOException; * Utility functions for reading {@code proc} files */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class ProcStatsUtil { private static final boolean DEBUG = false; @@ -92,10 +93,24 @@ public final class ProcStatsUtil { * seen, or at the end of the file */ @Nullable + @android.ravenwood.annotation.RavenwoodReplace public static String readTerminatedProcFile(String path, byte terminator) { // Permit disk reads here, as /proc isn't really "on disk" and should be fast. // TODO: make BlockGuard ignore /proc/ and /sys/ files perhaps? final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); + try { + return readTerminatedProcFileInternal(path, terminator); + } finally { + StrictMode.setThreadPolicy(savedPolicy); + } + } + + public static String readTerminatedProcFile$ravenwood(String path, byte terminator) { + // No StrictMode under Ravenwood + return readTerminatedProcFileInternal(path, terminator); + } + + private static String readTerminatedProcFileInternal(String path, byte terminator) { try (FileInputStream is = new FileInputStream(path)) { ByteArrayOutputStream byteStream = null; final byte[] buffer = new byte[READ_SIZE]; @@ -147,8 +162,6 @@ public final class ProcStatsUtil { Slog.d(TAG, "Failed to open proc file", e); } return null; - } finally { - StrictMode.setThreadPolicy(savedPolicy); } } } diff --git a/core/java/com/android/internal/os/StoragedUidIoStatsReader.java b/core/java/com/android/internal/os/StoragedUidIoStatsReader.java index 9b0346923cd3..2d485dac4859 100644 --- a/core/java/com/android/internal/os/StoragedUidIoStatsReader.java +++ b/core/java/com/android/internal/os/StoragedUidIoStatsReader.java @@ -38,6 +38,7 @@ import java.nio.file.Files; * This provides the number of bytes/chars read/written in foreground/background for each uid. * The file contains a monotonically increasing count of bytes/chars for a single boot. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class StoragedUidIoStatsReader { private static final String TAG = StoragedUidIoStatsReader.class.getSimpleName(); @@ -73,8 +74,21 @@ public class StoragedUidIoStatsReader { * * @param callback The callback to invoke for each line of the proc file. */ + @android.ravenwood.annotation.RavenwoodReplace public void readAbsolute(Callback callback) { final int oldMask = StrictMode.allowThreadDiskReadsMask(); + try { + readAbsoluteInternal(callback); + } finally { + StrictMode.setThreadPolicyMask(oldMask); + } + } + + public void readAbsolute$ravenwood(Callback callback) { + readAbsoluteInternal(callback); + } + + private void readAbsoluteInternal(Callback callback) { File file = new File(sUidIoFile); try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { String line; @@ -106,8 +120,6 @@ public class StoragedUidIoStatsReader { } } catch (IOException e) { Slog.e(TAG, "Failed to read " + sUidIoFile + ": " + e.getMessage()); - } finally { - StrictMode.setThreadPolicyMask(oldMask); } } } diff --git a/core/java/com/android/internal/pm/parsing/AppInfoUtils.java b/core/java/com/android/internal/pm/parsing/AppInfoUtils.java new file mode 100644 index 000000000000..38a2fe2a77a1 --- /dev/null +++ b/core/java/com/android/internal/pm/parsing/AppInfoUtils.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.pm.parsing; + +import android.annotation.CheckResult; +import android.content.pm.ApplicationInfo; + +import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.AndroidPackage; + +public class AppInfoUtils { + + /** + * @see ApplicationInfo#flags + */ + public static int appInfoFlags(AndroidPackage pkg) { + // @formatter:off + int pkgWithoutStateFlags = flag(pkg.isExternalStorage(), ApplicationInfo.FLAG_EXTERNAL_STORAGE) + | flag(pkg.isHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED) + | flag(pkg.isBackupAllowed(), ApplicationInfo.FLAG_ALLOW_BACKUP) + | flag(pkg.isKillAfterRestoreAllowed(), ApplicationInfo.FLAG_KILL_AFTER_RESTORE) + | flag(pkg.isRestoreAnyVersion(), ApplicationInfo.FLAG_RESTORE_ANY_VERSION) + | flag(pkg.isFullBackupOnly(), ApplicationInfo.FLAG_FULL_BACKUP_ONLY) + | flag(pkg.isPersistent(), ApplicationInfo.FLAG_PERSISTENT) + | flag(pkg.isDebuggable(), ApplicationInfo.FLAG_DEBUGGABLE) + | flag(pkg.isVmSafeMode(), ApplicationInfo.FLAG_VM_SAFE_MODE) + | flag(pkg.isDeclaredHavingCode(), ApplicationInfo.FLAG_HAS_CODE) + | flag(pkg.isTaskReparentingAllowed(), ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING) + | flag(pkg.isClearUserDataAllowed(), ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) + | flag(pkg.isLargeHeap(), ApplicationInfo.FLAG_LARGE_HEAP) + | flag(pkg.isCleartextTrafficAllowed(), ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) + | flag(pkg.isRtlSupported(), ApplicationInfo.FLAG_SUPPORTS_RTL) + | flag(pkg.isTestOnly(), ApplicationInfo.FLAG_TEST_ONLY) + | flag(pkg.isMultiArch(), ApplicationInfo.FLAG_MULTIARCH) + | flag(pkg.isExtractNativeLibrariesRequested(), ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) + | flag(pkg.isGame(), ApplicationInfo.FLAG_IS_GAME) + | flag(pkg.isSmallScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) + | flag(pkg.isNormalScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) + | flag(pkg.isLargeScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) + | flag(pkg.isExtraLargeScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) + | flag(pkg.isResizeable(), ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) + | flag(pkg.isAnyDensity(), ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) + | flag(AndroidPackageLegacyUtils.isSystem(pkg), ApplicationInfo.FLAG_SYSTEM) + | flag(pkg.isFactoryTest(), ApplicationInfo.FLAG_FACTORY_TEST); + + return pkgWithoutStateFlags; + // @formatter:on + } + + /** @see ApplicationInfo#privateFlags */ + public static int appInfoPrivateFlags(AndroidPackage pkg) { + // @formatter:off + int pkgWithoutStateFlags = flag(pkg.isStaticSharedLibrary(), ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) + | flag(pkg.isResourceOverlay(), ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY) + | flag(pkg.isIsolatedSplitLoading(), ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) + | flag(pkg.isHasDomainUrls(), ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) + | flag(pkg.isProfileableByShell(), ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL) + | flag(pkg.isBackupInForeground(), ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND) + | flag(pkg.isUseEmbeddedDex(), ApplicationInfo.PRIVATE_FLAG_USE_EMBEDDED_DEX) + | flag(pkg.isDefaultToDeviceProtectedStorage(), ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) + | flag(pkg.isDirectBootAware(), ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE) + | flag(pkg.isPartiallyDirectBootAware(), ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) + | flag(pkg.isClearUserDataOnFailedRestoreAllowed(), ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE) + | flag(pkg.isAllowAudioPlaybackCapture(), ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE) + | flag(pkg.isRequestLegacyExternalStorage(), ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) + | flag(pkg.isNonSdkApiRequested(), ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API) + | flag(pkg.isUserDataFragile(), ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) + | flag(pkg.isSaveStateDisallowed(), ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) + | flag(pkg.isResizeableActivityViaSdkVersion(), ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) + | flag(pkg.isAllowNativeHeapPointerTagging(), ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING) + | flag(AndroidPackageLegacyUtils.isSystemExt(pkg), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) + | flag(AndroidPackageLegacyUtils.isPrivileged(pkg), ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) + | flag(AndroidPackageLegacyUtils.isOem(pkg), ApplicationInfo.PRIVATE_FLAG_OEM) + | flag(AndroidPackageLegacyUtils.isVendor(pkg), ApplicationInfo.PRIVATE_FLAG_VENDOR) + | flag(AndroidPackageLegacyUtils.isProduct(pkg), ApplicationInfo.PRIVATE_FLAG_PRODUCT) + | flag(AndroidPackageLegacyUtils.isOdm(pkg), ApplicationInfo.PRIVATE_FLAG_ODM) + | flag(pkg.isSignedWithPlatformKey(), ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY); + + Boolean resizeableActivity = pkg.getResizeableActivity(); + if (resizeableActivity != null) { + if (resizeableActivity) { + pkgWithoutStateFlags |= ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE; + } else { + pkgWithoutStateFlags |= ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE; + } + } + + return pkgWithoutStateFlags; + // @formatter:on + } + + + /** @see ApplicationInfo#privateFlagsExt */ + public static int appInfoPrivateFlagsExt(AndroidPackage pkg, + boolean isAllowlistedForHiddenApis) { + // @formatter:off + int pkgWithoutStateFlags = flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE) + | flag(pkg.hasRequestForegroundServiceExemption(), ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION) + | flag(pkg.isAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE) + | flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK) + | flag(isAllowlistedForHiddenApis, ApplicationInfo.PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS); + return pkgWithoutStateFlags; + // @formatter:on + } + + @CheckResult + private static int flag(boolean hasFlag, int flag) { + return hasFlag ? flag : 0; + } +} diff --git a/core/java/com/android/internal/pm/parsing/PackageParserException.java b/core/java/com/android/internal/pm/parsing/PackageParserException.java new file mode 100644 index 000000000000..4250bbd9baf6 --- /dev/null +++ b/core/java/com/android/internal/pm/parsing/PackageParserException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.pm.parsing; + +public class PackageParserException extends Exception { + public final int error; + + public PackageParserException(int error, String detailMessage) { + super(detailMessage); + this.error = error; + } + + public PackageParserException(int error, String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + this.error = error; + } +}
\ No newline at end of file diff --git a/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageLegacyUtils.java b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageLegacyUtils.java new file mode 100644 index 000000000000..e65f1c960b6a --- /dev/null +++ b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageLegacyUtils.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.pm.parsing.pkg; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; + +import com.android.internal.pm.pkg.parsing.ParsingPackageHidden; +import com.android.server.pm.pkg.AndroidPackage; + +/** @hide */ +public class AndroidPackageLegacyUtils { + + private AndroidPackageLegacyUtils() { + } + + /** + * Returns the primary ABI as parsed from the package. Used only during parsing and derivation. + * Otherwise prefer {@link PackageState#getPrimaryCpuAbi()}. + */ + public static String getRawPrimaryCpuAbi(AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).getPrimaryCpuAbi(); + } + + /** + * Returns the secondary ABI as parsed from the package. Used only during parsing and + * derivation. Otherwise prefer {@link PackageState#getSecondaryCpuAbi()}. + */ + public static String getRawSecondaryCpuAbi(@NonNull AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi(); + } + + @Deprecated + @NonNull + public static ApplicationInfo generateAppInfoWithoutState(AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).toAppInfoWithoutState(); + } + + /** + * Replacement of unnecessary legacy getRealPackage. Only returns a value if the package was + * actually renamed. + */ + @Nullable + public static String getRealPackageOrNull(@NonNull AndroidPackage pkg, boolean isSystem) { + if (pkg.getOriginalPackages().isEmpty() || !isSystem) { + return null; + } + + return pkg.getManifestPackageName(); + } + + public static void fillVersionCodes(@NonNull AndroidPackage pkg, @NonNull PackageInfo info) { + info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode(); + info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor(); + } + + /** + * @deprecated Use {@link PackageState#isSystem} + */ + @Deprecated + public static boolean isSystem(@NonNull AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).isSystem(); + } + + /** + * @deprecated Use {@link PackageState#isSystemExt} + */ + @Deprecated + public static boolean isSystemExt(@NonNull AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).isSystemExt(); + } + + /** + * @deprecated Use {@link PackageState#isPrivileged} + */ + @Deprecated + public static boolean isPrivileged(@NonNull AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).isPrivileged(); + } + + /** + * @deprecated Use {@link PackageState#isOem} + */ + @Deprecated + public static boolean isOem(@NonNull AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).isOem(); + } + + /** + * @deprecated Use {@link PackageState#isVendor} + */ + @Deprecated + public static boolean isVendor(@NonNull AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).isVendor(); + } + + /** + * @deprecated Use {@link PackageState#isProduct} + */ + @Deprecated + public static boolean isProduct(@NonNull AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).isProduct(); + } + + /** + * @deprecated Use {@link PackageState#isOdm} + */ + @Deprecated + public static boolean isOdm(@NonNull AndroidPackage pkg) { + return ((AndroidPackageHidden) pkg).isOdm(); + } +} diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java index 85d95eab2958..f7e1f7293ac6 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.parsing.pkg; +package com.android.internal.pm.parsing.pkg; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -50,47 +50,44 @@ import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.parsing.pkg.AndroidPackageHidden; -import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; -import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.parsing.AppInfoUtils; import com.android.internal.pm.pkg.AndroidPackageSplitImpl; +import com.android.internal.pm.pkg.SEInfoUtil; +import com.android.internal.pm.pkg.component.ComponentMutateUtils; import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedActivityImpl; import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedApexSystemServiceImpl; import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedAttributionImpl; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl; +import com.android.internal.pm.pkg.component.ParsedPermissionImpl; import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProcessImpl; import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedProviderImpl; import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedServiceImpl; import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.pm.pkg.parsing.ParsingPackageHidden; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; -import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageSplit; -import com.android.server.pm.pkg.SELinuxUtil; -import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl; -import com.android.server.pm.pkg.component.ParsedAttributionImpl; -import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; -import com.android.server.pm.pkg.component.ParsedPermissionImpl; -import com.android.server.pm.pkg.component.ParsedProcessImpl; -import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedServiceImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; import libcore.util.EmptyArray; @@ -267,6 +264,8 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Nullable private String[][] usesSdkLibrariesCertDigests; @Nullable + private boolean[] usesSdkLibrariesOptional; + @Nullable @DataClass.ParcelWith(ForInternedString.class) private String sharedUserId; private int sharedUserLabel; @@ -420,8 +419,10 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @NonNull public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath, - @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) { - return new PackageImpl(packageName, baseCodePath, codePath, manifestArray, isCoreApp); + @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp, + @Nullable ParsingPackageUtils.Callback callback) { + return new PackageImpl( + packageName, baseCodePath, codePath, manifestArray, isCoreApp, callback); } /** @@ -451,7 +452,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @NonNull @VisibleForTesting public static ParsingPackage forTesting(String packageName, String baseCodePath) { - return new PackageImpl(packageName, baseCodePath, baseCodePath, null, false); + return new PackageImpl(packageName, baseCodePath, baseCodePath, null, false, null); } @NonNull @@ -718,16 +719,33 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Override public PackageImpl addUsesSdkLibrary(String libraryName, long versionMajor, - String[] certSha256Digests) { + String[] certSha256Digests, boolean usesSdkLibrariesOptional) { this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries, TextUtils.safeIntern(libraryName)); this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong( this.usesSdkLibrariesVersionsMajor, versionMajor, true); this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class, this.usesSdkLibrariesCertDigests, certSha256Digests, true); + this.usesSdkLibrariesOptional = appendBoolean(this.usesSdkLibrariesOptional, + usesSdkLibrariesOptional); return this; } + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + public static boolean[] appendBoolean(@Nullable boolean[] cur, boolean val) { + if (cur == null) { + return new boolean[] { val }; + } + final int N = cur.length; + boolean[] ret = new boolean[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + @Override public PackageImpl addUsesStaticLibrary(String libraryName, long version, String[] certSha256Digests) { @@ -1468,6 +1486,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Override public long[] getUsesSdkLibrariesVersionsMajor() { return usesSdkLibrariesVersionsMajor; } + @Nullable + @Override + public boolean[] getUsesSdkLibrariesOptional() { + return usesSdkLibrariesOptional; + } + @NonNull @Override public List<String> getUsesStaticLibraries() { @@ -2669,12 +2693,16 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, private String mBaseAppDataCredentialProtectedDirForSystemUser; private String mBaseAppDataDeviceProtectedDirForSystemUser; + ParsingPackageUtils.Callback mCallback; + @VisibleForTesting public PackageImpl(@NonNull String packageName, @NonNull String baseApkPath, - @NonNull String path, @Nullable TypedArray manifestArray, boolean isCoreApp) { + @NonNull String path, @Nullable TypedArray manifestArray, boolean isCoreApp, + @Nullable ParsingPackageUtils.Callback callback) { this.packageName = TextUtils.safeIntern(packageName); this.mBaseApkPath = baseApkPath; this.mPath = path; + this.mCallback = callback; if (manifestArray != null) { versionCode = manifestArray.getInteger(R.styleable.AndroidManifest_versionCode, 0); @@ -2725,9 +2753,11 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } private void assignDerivedFields2() { - mBaseAppInfoFlags = PackageInfoUtils.appInfoFlags(this, null); - mBaseAppInfoPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(this, null); - mBaseAppInfoPrivateFlagsExt = PackageInfoUtils.appInfoPrivateFlagsExt(this, null); + mBaseAppInfoFlags = AppInfoUtils.appInfoFlags(this); + mBaseAppInfoPrivateFlags = AppInfoUtils.appInfoPrivateFlags(this); + mBaseAppInfoPrivateFlagsExt = AppInfoUtils.appInfoPrivateFlagsExt(this, + mCallback == null ? false : + mCallback.getHiddenApiWhitelistedApps().contains(this.packageName)); String baseAppDataDir = Environment.getDataDirectoryPath(getVolumeUuid()) + File.separator; String systemUserSuffix = File.separator + UserHandle.USER_SYSTEM + File.separator; mBaseAppDataCredentialProtectedDirForSystemUser = TextUtils.safeIntern( @@ -3062,7 +3092,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, appInfo.primaryCpuAbi = primaryCpuAbi; appInfo.secondaryCpuAbi = secondaryCpuAbi; appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir; - appInfo.seInfoUser = SELinuxUtil.COMPLETE_STR; + appInfo.seInfoUser = SEInfoUtil.COMPLETE_STR; appInfo.uid = uid; return appInfo; } @@ -3126,6 +3156,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, dest.writeStringArray(this.usesSdkLibrariesCertDigests[index]); } } + dest.writeBooleanArray(this.usesSdkLibrariesOptional); sForInternedString.parcel(this.sharedUserId, dest, flags); dest.writeInt(this.sharedUserLabel); @@ -3278,6 +3309,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } } } + this.usesSdkLibrariesOptional = in.createBooleanArray(); this.sharedUserId = sForInternedString.unparcel(in); this.sharedUserLabel = in.readInt(); diff --git a/services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java b/core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java index d9625050d28c..a670c6d5aff4 100644 --- a/services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java +++ b/core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.permission; +package com.android.internal.pm.permission; import android.Manifest; import android.annotation.NonNull; @@ -67,7 +67,7 @@ public class CompatibilityPermissionInfo { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -97,10 +97,10 @@ public class CompatibilityPermissionInfo { } @DataClass.Generated( - time = 1627674427184L, + time = 1701338392152L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mName\nprivate final int mSdkVersion\npublic static final android.content.pm.permission.CompatibilityPermissionInfo[] COMPAT_PERMS\nclass CompatibilityPermissionInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genBuilder=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java", + inputSignatures = "private final @android.annotation.NonNull java.lang.String mName\nprivate final int mSdkVersion\npublic static final com.android.internal.pm.permission.CompatibilityPermissionInfo[] COMPAT_PERMS\nclass CompatibilityPermissionInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/core/java/com/android/internal/pm/pkg/SEInfoUtil.java b/core/java/com/android/internal/pm/pkg/SEInfoUtil.java new file mode 100644 index 000000000000..a6988829ca92 --- /dev/null +++ b/core/java/com/android/internal/pm/pkg/SEInfoUtil.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.pm.pkg; + +/** + * Utility methods that need to be used in application space. + * @hide + */ +public final class SEInfoUtil { + + /** Append to existing seinfo label for instant apps @hide */ + public static final String INSTANT_APP_STR = ":ephemeralapp"; + + /** Append to existing seinfo when modifications are complete @hide */ + public static final String COMPLETE_STR = ":complete"; +} diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentMutateUtils.java index 1964df0853fd..fd5f0f079da9 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ComponentMutateUtils.java @@ -14,19 +14,11 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.internal.pm.pkg.component.ParsedActivity; -import com.android.internal.pm.pkg.component.ParsedComponent; -import com.android.internal.pm.pkg.component.ParsedMainComponent; -import com.android.internal.pm.pkg.component.ParsedPermission; -import com.android.internal.pm.pkg.component.ParsedPermissionGroup; -import com.android.internal.pm.pkg.component.ParsedProcess; -import com.android.internal.pm.pkg.component.ParsedProvider; - /** * Contains mutation methods so that code doesn't have to cast to the Impl. Meant to eventually * be removed once all post-parsing mutation is moved to parsing. diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java index 019ca1315af8..db08005c833e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.AttrRes; import android.annotation.NonNull; @@ -29,14 +29,9 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; -import com.android.internal.pm.pkg.component.ParsedComponent; -import com.android.internal.pm.pkg.component.ParsedIntentInfo; -import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.PackageUserState; -import com.android.server.pm.pkg.PackageUserStateUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -173,16 +168,4 @@ public class ComponentParseUtils { public static int getIcon(ParsedComponent component) { return component.getIcon(); } - - public static boolean isMatch(PackageUserState state, boolean isSystem, - boolean isPackageEnabled, ParsedMainComponent component, long flags) { - return PackageUserStateUtils.isMatch(state, isSystem, isPackageEnabled, - component.isEnabled(), component.isDirectBootAware(), component.getName(), flags); - } - - public static boolean isEnabled(PackageUserState state, boolean isPackageEnabled, - ParsedMainComponent parsedComponent, long flags) { - return PackageUserStateUtils.isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), - parsedComponent.getName(), flags); - } } diff --git a/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java index dd54cfca6518..0b045919fb13 100644 --- a/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java +++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.content.pm.parsing.result.ParseInput; @@ -27,7 +27,6 @@ import android.util.ArraySet; import com.android.internal.R; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.SystemConfig; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -48,9 +47,8 @@ public class InstallConstraintsTagParser { * @hide */ public static ParseResult<ParsingPackage> parseInstallConstraints( - ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) - throws XmlPullParserException, IOException { - Set<String> allowlist = SystemConfig.getInstance().getInstallConstraintsAllowlist(); + ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, + Set<String> allowlist) throws XmlPullParserException, IOException { if (!allowlist.contains(pkg.getPackageName())) { return input.skip("install-constraints cannot be used by this package"); } diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java index f02790189cc0..2f977eea127d 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; @@ -22,8 +22,8 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VER import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; -import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString; -import static com.android.server.pm.parsing.pkg.PackageImpl.sForStringSet; +import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString; +import static com.android.internal.pm.parsing.pkg.PackageImpl.sForStringSet; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,10 +36,9 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.ArraySet; -import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; -import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import java.util.Collections; import java.util.Locale; @@ -380,7 +379,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -696,10 +695,10 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse } @DataClass.Generated( - time = 1669437519576L, + time = 1701338377709L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java", - inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java", + inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java index 64985bdfd54f..c3f7dab3c46a 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; -import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; -import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts; +import static com.android.internal.pm.pkg.component.ComponentParseUtils.flag; +import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET; +import static com.android.internal.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts; import android.annotation.NonNull; import android.annotation.Nullable; @@ -48,11 +48,10 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import com.android.internal.util.ArrayUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java index cfed19aa0934..27f7eee1a308 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; @@ -22,7 +22,6 @@ import android.annotation.Nullable; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -60,7 +59,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -248,9 +247,9 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par }; @DataClass.Generated( - time = 1643723578605L, + time = 1701710844088L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java", + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java", inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceUtils.java index d3fb29b8aa66..c69213f11f85 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.R; import android.annotation.NonNull; @@ -25,8 +25,6 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; -import com.android.internal.pm.pkg.component.ParsedApexSystemService; - import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java index 62b994724346..e3bfb38802db 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.StringRes; @@ -22,7 +22,6 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedAttribution; import com.android.internal.util.DataClass; import java.util.ArrayList; @@ -60,7 +59,7 @@ public class ParsedAttributionImpl implements ParsedAttribution, Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -207,10 +206,10 @@ public class ParsedAttributionImpl implements ParsedAttribution, Parcelable { }; @DataClass.Generated( - time = 1641431950829L, + time = 1701338881658L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java", - inputSignatures = "static final int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution, android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java", + inputSignatures = "static final int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedAttribution, android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionUtils.java index 411220ae42e8..ee5c3204ccd1 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,7 +26,6 @@ import android.content.res.XmlResourceParser; import android.util.ArraySet; import com.android.internal.R; -import com.android.internal.pm.pkg.component.ParsedAttribution; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java index 512e5c7023c7..7ee22f30ace0 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString; +import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString; import static java.util.Collections.emptyMap; @@ -32,12 +32,10 @@ import android.text.TextUtils; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedComponent; -import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; -import com.android.server.pm.pkg.parsing.ParsingUtils; import java.util.ArrayList; import java.util.Collections; @@ -200,7 +198,7 @@ public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -306,10 +304,10 @@ public abstract class ParsedComponentImpl implements ParsedComponent, Parcelable } @DataClass.Generated( - time = 1641414207885L, + time = 1701445673589L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java", - inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate int icon\nprivate int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate int logo\nprivate int banner\nprivate int descriptionRes\nprivate int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfoImpl> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\n void addIntent(android.content.pm.parsing.component.ParsedIntentInfoImpl)\n void addProperty(android.content.pm.PackageManager.Property)\npublic android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @android.annotation.NonNull @java.lang.Override android.os.Bundle getMetaData()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> getIntents()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false, genParcelable=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java", + inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate int icon\nprivate int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate int logo\nprivate int banner\nprivate int descriptionRes\nprivate int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<com.android.internal.pm.pkg.component.ParsedIntentInfoImpl> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\npublic void addIntent(com.android.internal.pm.pkg.component.ParsedIntentInfoImpl)\npublic void addProperty(android.content.pm.PackageManager.Property)\npublic com.android.internal.pm.pkg.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @android.annotation.NonNull @java.lang.Override android.os.Bundle getMetaData()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.internal.pm.pkg.component.ParsedIntentInfo> getIntents()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponentUtils.java index 9322cf0e90f6..9e2548b3bce3 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponentUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; +import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.content.pm.PackageManager; @@ -32,8 +32,8 @@ import android.util.TypedValue; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; /** @hide */ class ParsedComponentUtils { diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java index 7bfad14d669a..07322e9dd912 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString; +import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,7 +26,6 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -112,7 +111,7 @@ public class ParsedInstrumentationImpl extends ParsedComponentImpl implements // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -166,10 +165,10 @@ public class ParsedInstrumentationImpl extends ParsedComponentImpl implements } @DataClass.Generated( - time = 1641431951575L, + time = 1701445763455L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java", - inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate boolean handleProfiling\nprivate boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java", + inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate boolean handleProfiling\nprivate boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedInstrumentationImpl> CREATOR\npublic com.android.internal.pm.pkg.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic com.android.internal.pm.pkg.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedInstrumentation, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationUtils.java index a7116949b911..661c8b421fb4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; +import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.content.pm.parsing.result.ParseInput; @@ -26,7 +26,6 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import com.android.internal.R; -import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.pm.pkg.parsing.ParsingPackage; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java index ab9404310078..adb49e9fde6d 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -23,7 +23,6 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.DataClass; /** @@ -54,7 +53,7 @@ public class ParsedIntentInfoImpl implements ParsedIntentInfo, Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -170,10 +169,10 @@ public class ParsedIntentInfoImpl implements ParsedIntentInfo, Parcelable { }; @DataClass.Generated( - time = 1641431952314L, + time = 1701445800363L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java", - inputSignatures = "private boolean mHasDefault\nprivate int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java", + inputSignatures = "private boolean mHasDefault\nprivate int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedIntentInfo, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java index e5e214d2292b..c6683cfc8331 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; +import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; import android.annotation.NonNull; import android.content.Intent; @@ -31,10 +31,9 @@ import android.util.Slog; import android.util.TypedValue; import com.android.internal.R; -import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java index f322eef8c3a3..bb8f565d2032 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString; +import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; @@ -25,7 +25,6 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -133,7 +132,7 @@ public class ParsedMainComponentImpl extends ParsedComponentImpl implements Pars // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -227,10 +226,10 @@ public class ParsedMainComponentImpl extends ParsedComponentImpl implements Pars } @DataClass.Generated( - time = 1641414540422L, + time = 1701447884766L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java", - inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate boolean directBootAware\nprivate boolean enabled\nprivate boolean exported\nprivate int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java", + inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate boolean directBootAware\nprivate boolean enabled\nprivate boolean exported\nprivate int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedMainComponentImpl> CREATOR\npublic com.android.internal.pm.pkg.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java index 8268f0fdfa3e..7e56180f72ce 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; +import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,10 +31,8 @@ import android.os.Build; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedIntentInfo; -import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java index afe37bc3274c..3622019f36b9 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.DataClass; /** @@ -75,7 +74,7 @@ public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -172,10 +171,10 @@ public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements }; @DataClass.Generated( - time = 1642132854167L, + time = 1701445837884L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java", - inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java", + inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java index 69e33c8f281e..4dcce131168b 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -24,8 +24,6 @@ import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedPermission; -import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -148,7 +146,7 @@ public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedP // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -162,7 +160,7 @@ public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedP int requestRes, int protectionLevel, boolean tree, - @Nullable ParsedPermissionGroupImpl parsedPermissionGroup, + @Nullable ParsedPermissionGroup parsedPermissionGroup, @Nullable Set<String> knownCerts) { this.backgroundPermission = backgroundPermission; this.group = group; @@ -237,10 +235,10 @@ public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedP } @DataClass.Generated( - time = 1641414649731L, + time = 1701445829812L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java", - inputSignatures = "private static com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate int requestRes\nprivate int protectionLevel\nprivate boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroupImpl parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedPermissionGroup getParsedPermissionGroup()\npublic android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected void setKnownCert(java.lang.String)\nprotected void setKnownCerts(java.lang.String[])\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownCerts()\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java", + inputSignatures = "private static final com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate int requestRes\nprivate int protectionLevel\nprivate boolean tree\nprivate @android.annotation.Nullable com.android.internal.pm.pkg.component.ParsedPermissionGroup parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedPermissionImpl> CREATOR\npublic com.android.internal.pm.pkg.component.ParsedPermissionGroup getParsedPermissionGroup()\npublic com.android.internal.pm.pkg.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected void setKnownCert(java.lang.String)\nprotected void setKnownCerts(java.lang.String[])\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownCerts()\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java index 4b45d3742a2a..5651c1ca247f 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; +import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.content.pm.PermissionInfo; @@ -31,10 +31,8 @@ import android.util.EventLog; import android.util.Slog; import com.android.internal.R; -import com.android.internal.pm.pkg.component.ParsedPermission; -import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java index 40e3670b9261..212fb867e7df 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import static java.util.Collections.emptySet; @@ -25,7 +25,6 @@ import android.os.Parcelable; import android.util.ArrayMap; import android.util.ArraySet; -import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -98,7 +97,7 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -304,10 +303,10 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable { }; @DataClass.Generated( - time = 1641431953775L, + time = 1701445656489L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java", - inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java", + inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java index a84954950f44..3b2056e7892e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.content.pm.ApplicationInfo; @@ -27,11 +27,10 @@ import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.R; -import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.XmlUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java index 81a3c17e2bb4..987fd4158418 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString; +import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,7 +28,6 @@ import android.os.PatternMatcher; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -302,7 +301,7 @@ public class ParsedProviderImpl extends ParsedMainComponentImpl implements Parse time = 1642560323360L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java", - inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") + inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.internal.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.internal.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java index 0b28a1214072..5d82d0469d56 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; -import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; +import static com.android.internal.pm.pkg.component.ComponentParseUtils.flag; +import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,9 +34,8 @@ import android.os.PatternMatcher; import android.util.Slog; import com.android.internal.R; -import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java index ca8c45d1383c..f4662d803296 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString; +import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,8 +26,6 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedMainComponent; -import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -107,7 +105,7 @@ public class ParsedServiceImpl extends ParsedMainComponentImpl implements Parsed // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -141,10 +139,10 @@ public class ParsedServiceImpl extends ParsedMainComponentImpl implements Parsed } @DataClass.Generated( - time = 1641431954479L, + time = 1701445638370L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java", - inputSignatures = "private int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java", + inputSignatures = "private int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedServiceImpl> CREATOR\npublic com.android.internal.pm.pkg.component.ParsedMainComponent setPermission(java.lang.String)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java index 171ef594f6fd..a1dd19a3bc90 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; -import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; +import static com.android.internal.pm.pkg.component.ComponentParseUtils.flag; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,9 +32,8 @@ import android.content.res.XmlResourceParser; import android.os.Build; import com.android.internal.R; -import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java index 78377a836651..fd131dfd00c7 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -51,7 +50,7 @@ public class ParsedUsesPermissionImpl implements ParsedUsesPermission, Parcelabl // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -158,10 +157,10 @@ public class ParsedUsesPermissionImpl implements ParsedUsesPermission, Parcelabl }; @DataClass.Generated( - time = 1641431955242L, + time = 1701445626268L, codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java", - inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") + sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java", + inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.pm.pkg.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedUsesPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") @Deprecated private void __metadata() {} diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index 4ed361f24439..6c09b7c04fa7 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -112,7 +112,7 @@ public interface ParsingPackage { ParsingPackage addUsesOptionalNativeLibrary(String libraryName); ParsingPackage addUsesSdkLibrary(String libraryName, long versionMajor, - String[] certSha256Digests); + String[] certSha256Digests, boolean usesSdkLibrariesOptional); ParsingPackage addUsesStaticLibrary(String libraryName, long version, String[] certSha256Digests); diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index 722350a0d7fb..dbe4fba5dfdb 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.parsing; +package com.android.internal.pm.pkg.parsing; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; @@ -30,7 +30,7 @@ import static android.os.Build.VERSION_CODES.DONUT; import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; -import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts; +import static com.android.internal.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts; import android.annotation.AnyRes; import android.annotation.CheckResult; @@ -57,7 +57,6 @@ import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseInput.DeferredError; import android.content.pm.parsing.result.ParseResult; -import android.content.pm.parsing.result.ParseTypeImpl; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; @@ -90,43 +89,40 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.R; import com.android.internal.os.ClassLoaderFactory; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.permission.CompatibilityPermissionInfo; +import com.android.internal.pm.pkg.component.ComponentMutateUtils; +import com.android.internal.pm.pkg.component.ComponentParseUtils; +import com.android.internal.pm.pkg.component.InstallConstraintsTagParser; import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedActivityImpl; +import com.android.internal.pm.pkg.component.ParsedActivityUtils; import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedApexSystemServiceUtils; import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedAttributionUtils; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedInstrumentationUtils; import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl; +import com.android.internal.pm.pkg.component.ParsedIntentInfoUtils; import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedPermissionUtils; import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProcessUtils; import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedProviderUtils; import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedServiceUtils; import com.android.internal.pm.pkg.component.ParsedUsesPermission; -import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; +import com.android.internal.pm.split.DefaultSplitAssetLoader; +import com.android.internal.pm.split.SplitAssetDependencyLoader; +import com.android.internal.pm.split.SplitAssetLoader; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; -import com.android.server.pm.SharedUidMigration; -import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.permission.CompatibilityPermissionInfo; -import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ComponentParseUtils; -import com.android.server.pm.pkg.component.InstallConstraintsTagParser; -import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedActivityUtils; -import com.android.server.pm.pkg.component.ParsedApexSystemServiceUtils; -import com.android.server.pm.pkg.component.ParsedAttributionUtils; -import com.android.server.pm.pkg.component.ParsedInstrumentationUtils; -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfoUtils; -import com.android.server.pm.pkg.component.ParsedPermissionUtils; -import com.android.server.pm.pkg.component.ParsedProcessUtils; -import com.android.server.pm.pkg.component.ParsedProviderUtils; -import com.android.server.pm.pkg.component.ParsedServiceUtils; -import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; -import com.android.server.pm.split.DefaultSplitAssetLoader; -import com.android.server.pm.split.SplitAssetDependencyLoader; -import com.android.server.pm.split.SplitAssetLoader; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -267,18 +263,6 @@ public class ParsingPackageUtils { public @interface ParseFlags {} /** - * @see #parseDefault(ParseInput, File, int, List, boolean) - */ - @NonNull - public static ParseResult<ParsedPackage> parseDefaultOneTime(File file, - @ParseFlags int parseFlags, - @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions, - boolean collectCertificates) { - ParseInput input = ParseTypeImpl.forDefaultParsing().reset(); - return parseDefault(input, file, parseFlags, splitPermissions, collectCertificates); - } - - /** * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off * request, without caching the input object and without querying the internal system state for * feature support. @@ -287,30 +271,10 @@ public class ParsingPackageUtils { public static ParseResult<ParsedPackage> parseDefault(ParseInput input, File file, @ParseFlags int parseFlags, @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions, - boolean collectCertificates) { - ParseResult<ParsedPackage> result; + boolean collectCertificates, Callback callback) { ParsingPackageUtils parser = new ParsingPackageUtils(null /*separateProcesses*/, - null /*displayMetrics*/, splitPermissions, new Callback() { - @Override - public boolean hasFeature(String feature) { - // Assume the device doesn't support anything. This will affect permission - // parsing and will force <uses-permission/> declarations to include all - // requiredNotFeature permissions and exclude all requiredFeature - // permissions. This mirrors the old behavior. - return false; - } - - @Override - public ParsingPackage startParsingPackage( - @NonNull String packageName, - @NonNull String baseApkPath, - @NonNull String path, - @NonNull TypedArray manifestArray, boolean isCoreApp) { - return PackageImpl.forParsing(packageName, baseApkPath, path, manifestArray, - isCoreApp); - } - }); + null /*displayMetrics*/, splitPermissions, callback); var parseResult = parser.parsePackage(input, file, parseFlags); if (parseResult.isError()) { return input.error(parseResult); @@ -1146,7 +1110,8 @@ public class ParsingPackageUtils { case TAG_RESTRICT_UPDATE: return parseRestrictUpdateHash(flags, input, pkg, res, parser); case TAG_INSTALL_CONSTRAINTS: - return parseInstallConstraints(input, pkg, res, parser); + return parseInstallConstraints(input, pkg, res, parser, + mCallback.getInstallConstraintsAllowlist()); case TAG_QUERIES: return parseQueries(input, pkg, res, parser); default: @@ -1172,7 +1137,7 @@ public class ParsingPackageUtils { } boolean leaving = false; - if (!SharedUidMigration.isDisabled()) { + if (PackageManager.ENABLE_SHARED_UID_MIGRATION) { int max = anInteger(0, R.styleable.AndroidManifest_sharedUserMaxSdkVersion, sa); leaving = (max != 0) && (max < Build.VERSION.RESOURCES_SDK_INT); } @@ -1858,10 +1823,11 @@ public class ParsingPackageUtils { return input.success(pkg); } - private static ParseResult<ParsingPackage> parseInstallConstraints( - ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) + private static ParseResult<ParsingPackage> parseInstallConstraints(ParseInput input, + ParsingPackage pkg, Resources res, XmlResourceParser parser, Set<String> allowlist) throws IOException, XmlPullParserException { - return InstallConstraintsTagParser.parseInstallConstraints(input, pkg, res, parser); + return InstallConstraintsTagParser.parseInstallConstraints( + input, pkg, res, parser, allowlist); } private static ParseResult<ParsingPackage> parseQueries(ParseInput input, ParsingPackage pkg, @@ -2598,6 +2564,8 @@ public class ParsingPackageUtils { R.styleable.AndroidManifestUsesSdkLibrary_versionMajor, -1); String certSha256Digest = sa.getNonResourceString(R.styleable .AndroidManifestUsesSdkLibrary_certDigest); + boolean optional = + sa.getBoolean(R.styleable.AndroidManifestUsesSdkLibrary_optional, false); // Since an APK providing a static shared lib can only provide the lib - fail if // malformed @@ -2641,7 +2609,8 @@ public class ParsingPackageUtils { System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests, 1, additionalCertSha256Digests.length); - return input.success(pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests)); + return input.success( + pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests, optional)); } finally { sa.recycle(); } @@ -3482,5 +3451,9 @@ public class ParsingPackageUtils { ParsingPackage startParsingPackage(@NonNull String packageName, @NonNull String baseApkPath, @NonNull String path, @NonNull TypedArray manifestArray, boolean isCoreApp); + + @NonNull Set<String> getHiddenApiWhitelistedApps(); + + @NonNull Set<String> getInstallConstraintsAllowlist(); } } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingUtils.java index 1d159554e8a7..26822c649db7 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.server.pm.pkg.parsing; +package com.android.internal.pm.pkg.parsing; -import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; +import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,10 +31,9 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.pm.pkg.component.ParsedIntentInfo; -import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl; import com.android.internal.util.Parcelling; import com.android.internal.util.XmlUtils; -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/core/java/com/android/internal/pm/split/DefaultSplitAssetLoader.java index 0bb969f488fe..50c62437b192 100644 --- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java +++ b/core/java/com/android/internal/pm/split/DefaultSplitAssetLoader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.pm.split; +package com.android.internal.pm.split; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; @@ -21,9 +21,9 @@ import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; import com.android.internal.util.ArrayUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; import libcore.io.IoUtils; diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/core/java/com/android/internal/pm/split/SplitAssetDependencyLoader.java index 56d92fbc95a2..c166cdcf9451 100644 --- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java +++ b/core/java/com/android/internal/pm/split/SplitAssetDependencyLoader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.pm.split; +package com.android.internal.pm.split; import android.annotation.NonNull; import android.content.pm.parsing.ApkLiteParseUtils; @@ -24,8 +24,8 @@ import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; import libcore.io.IoUtils; diff --git a/services/core/java/com/android/server/pm/split/SplitAssetLoader.java b/core/java/com/android/internal/pm/split/SplitAssetLoader.java index 845015916e60..c7c409d826ba 100644 --- a/services/core/java/com/android/server/pm/split/SplitAssetLoader.java +++ b/core/java/com/android/internal/pm/split/SplitAssetLoader.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.pm.split; +package com.android.internal.pm.split; import android.content.res.ApkAssets; import android.content.res.AssetManager; diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index dd310dc3922a..201b23cc172c 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -118,6 +118,7 @@ import android.window.OnBackInvokedDispatcher; import android.window.ProxyOnBackInvokedDispatcher; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.view.menu.ContextMenuBuilder; import com.android.internal.view.menu.IconMenuPresenter; import com.android.internal.view.menu.ListMenuPresenter; @@ -374,7 +375,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { boolean mDecorFitsSystemWindows = true; - private final boolean mDefaultEdgeToEdge; + @VisibleForTesting + public final boolean mDefaultEdgeToEdge; private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher; @@ -2448,6 +2450,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // Apply data from current theme. TypedArray a = getWindowStyle(); + WindowManager.LayoutParams params = getAttributes(); if (false) { System.out.println("From style:"); @@ -2467,8 +2470,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { setFlags(0, flagsToUpdate); } else { setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); - getAttributes().setFitInsetsSides(0); - getAttributes().setFitInsetsTypes(0); + params.setFitInsetsSides(0); + params.setFitInsetsTypes(0); + if (mDefaultEdgeToEdge) { + params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + } } if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { @@ -2586,8 +2592,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { R.styleable.Window_enforceNavigationBarContrast, true); } - WindowManager.LayoutParams params = getAttributes(); - // Non-floating windows on high end devices must put up decor beneath the system bars and // therefore must know about visibility changes of those. if (!mIsFloating) { diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 4bb7c33b41e2..8c2a52560050 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -93,6 +93,7 @@ public enum ProtoLogGroup implements IProtoLogGroup { WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), + WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 28fd2b488426..bf8e6135fd01 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -897,13 +897,26 @@ public class LockPatternUtils { } /** - * Returns true if {@code userHandle} is a managed profile with separate challenge. + * Returns true if {@code userHandle} is a profile with separate challenge. + * <p> + * Returns false if {@code userHandle} is a profile with unified challenge, a profile whose + * credential is not shareable with its parent, or a non-profile user. */ public boolean isSeparateProfileChallengeEnabled(int userHandle) { return isCredentialSharableWithParent(userHandle) && hasSeparateChallenge(userHandle); } /** + * Returns true if {@code userHandle} is a profile with unified challenge. + * <p> + * Returns false if {@code userHandle} is a profile with separate challenge, a profile whose + * credential is not shareable with its parent, or a non-profile user. + */ + public boolean isProfileWithUnifiedChallenge(int userHandle) { + return isCredentialSharableWithParent(userHandle) && !hasSeparateChallenge(userHandle); + } + + /** * Returns true if {@code userHandle} is a managed profile with unified challenge. */ public boolean isManagedProfileWithUnifiedChallenge(int userHandle) { diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java index c88763ce6c97..18d5f6db6ac9 100644 --- a/core/java/com/android/internal/widget/LockscreenCredential.java +++ b/core/java/com/android/internal/widget/LockscreenCredential.java @@ -134,12 +134,12 @@ public class LockscreenCredential implements Parcelable, AutoCloseable { } /** - * Creates a LockscreenCredential object representing a managed password for profile with - * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now. - * TODO: consider add a new credential type for this. This can then supersede the - * isLockTiedToParent argument in various places in LSS. + * Creates a LockscreenCredential object representing the system-generated, system-managed + * password for a profile with unified challenge. This credential has type {@code + * CREDENTIAL_TYPE_PASSWORD} for now. TODO: consider add a new credential type for this. This + * can then supersede the isLockTiedToParent argument in various places in LSS. */ - public static LockscreenCredential createManagedPassword(@NonNull byte[] password) { + public static LockscreenCredential createUnifiedProfilePassword(@NonNull byte[] password) { return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD, Arrays.copyOf(password, password.length), /* hasInvalidChars= */ false); } diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java index 4e4f26cd6ad6..adb0c6959f12 100644 --- a/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -58,6 +58,7 @@ import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import java.security.PublicKey; import java.util.List; @@ -690,7 +691,7 @@ public interface AndroidPackage { /** * The names of packages to adopt ownership of permissions from, parsed under {@link - * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. + * ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. * * @see R.styleable#AndroidManifestOriginalPackage_name * @hide @@ -795,7 +796,7 @@ public interface AndroidPackage { /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link - * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_KEY_SETS}. + * ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestKeySet * @see R.styleable#AndroidManifestPublicKey @@ -1266,7 +1267,7 @@ public interface AndroidPackage { /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link - * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_KEY_SETS}. + * ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestUpgradeKeySet * @hide @@ -1340,6 +1341,15 @@ public interface AndroidPackage { @Nullable long[] getUsesSdkLibrariesVersionsMajor(); + + /** + * @see R.styleable#AndroidManifestUsesSdkLibrary_optional + * @hide + */ + @Immutable.Ignore + @Nullable + boolean[] getUsesSdkLibrariesOptional(); + /** * TODO(b/135203078): Move static library stuff to an inner data class * diff --git a/core/jni/Android.bp b/core/jni/Android.bp index f365dbb1d46a..2a744e343ccd 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -49,8 +49,6 @@ cc_library_shared_for_libandroid_runtime { "-Wno-unused-parameter", "-Wunused", "-Wunreachable-code", - - "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash", ], cppflags: ["-Wno-conversion-null"], @@ -284,8 +282,6 @@ cc_library_shared_for_libandroid_runtime { "libscrypt_static", "libstatssocket_lazy", "libskia", - "libtextclassifier_hash_static", - "libexpresslog_jni", ], shared_libs: [ @@ -372,7 +368,6 @@ cc_library_shared_for_libandroid_runtime { "bionic_libc_platform_headers", "dnsproxyd_protocol_headers", "flatbuffer_headers", - "libtextclassifier_hash_headers", "tensorflow_headers", ], runtime_libs: [ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 50253cf9e457..c24d21dda68c 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -201,7 +201,6 @@ extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env); -extern int register_com_android_modules_expresslog_Utils(JNIEnv* env); extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); @@ -1590,7 +1589,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_incremental_IncrementalManager), REG_JNI(register_com_android_internal_content_om_OverlayConfig), REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl), - REG_JNI(register_com_android_modules_expresslog_Utils), REG_JNI(register_com_android_internal_net_NetworkUtilsInternal), REG_JNI(register_com_android_internal_os_ClassLoaderFactory), REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter), diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index e0bcef642d82..de1ce4e29198 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -1014,6 +1014,10 @@ static jboolean android_os_Debug_isVmapStack(JNIEnv *env, jobject clazz) return cfg_state == CONFIG_SET; } +static jboolean android_os_Debug_logAllocatorStats(JNIEnv*, jobject) { + return mallopt(M_LOG_STATS, 0) == 1 ? JNI_TRUE : JNI_FALSE; +} + /* * JNI registration. */ @@ -1056,6 +1060,7 @@ static const JNINativeMethod gMethods[] = { {"getDmabufHeapPoolsSizeKb", "()J", (void*)android_os_Debug_getDmabufHeapPoolsSizeKb}, {"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb}, {"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack}, + {"logAllocatorStats", "()Z", (void*)android_os_Debug_logAllocatorStats}, }; int register_android_os_Debug(JNIEnv *env) diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index dab47e9a9e28..76b05eac82af 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -107,7 +107,7 @@ static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, *vector = counter->getCount(state); } -static jobject native_toString(JNIEnv *env, jobject self, jlong nativePtr) { +static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) { battery::LongArrayMultiStateCounter *counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); return env->NewStringUTF(counter->toString().c_str()); @@ -127,7 +127,7 @@ static void throwWriteRE(JNIEnv *env, binder_status_t status) { } \ } -static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel, +static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel, jint flags) { battery::LongArrayMultiStateCounter *counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); @@ -161,7 +161,7 @@ static void throwReadException(JNIEnv *env, binder_status_t status) { } \ } -static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) { +static jlong native_initFromParcel(JNIEnv *env, jclass, jobject jParcel) { ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel)); int32_t stateCount; @@ -253,7 +253,7 @@ static jlong native_getReleaseFunc_LongArrayContainer() { return reinterpret_cast<jlong>(native_dispose_LongArrayContainer); } -static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, +static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, jlongArray jarray) { std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); ScopedLongArrayRO scopedArray(env, jarray); @@ -264,7 +264,7 @@ static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong std::copy(array, array + size, vector->data()); } -static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, +static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, jlongArray jarray) { std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); ScopedLongArrayRW scopedArray(env, jarray); @@ -273,7 +273,7 @@ static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get()); } -static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, +static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, jlongArray jarray, jintArray jindexMap) { std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); ScopedLongArrayRW scopedArray(env, jarray); diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp index 87ab4969040e..54c4cd50a902 100644 --- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp +++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp @@ -63,6 +63,7 @@ class NativeCommandBuffer { std::optional<std::pair<char*, char*>> readLine(FailFn fail_fn) { char* result = mBuffer + mNext; while (true) { + // We have scanned up to, but not including mNext for this line's newline. if (mNext == mEnd) { if (mEnd == MAX_COMMAND_BYTES) { return {}; @@ -89,7 +90,7 @@ class NativeCommandBuffer { } else { mNext = nl - mBuffer + 1; if (--mLinesLeft < 0) { - fail_fn("ZygoteCommandBuffer.readLine attempted to read past mEnd of command"); + fail_fn("ZygoteCommandBuffer.readLine attempted to read past end of command"); } return std::make_pair(result, nl); } @@ -125,8 +126,8 @@ class NativeCommandBuffer { mEnd += lineLen + 1; } - // Clear mBuffer, start reading new command, return the number of arguments, leaving mBuffer - // positioned at the beginning of first argument. Return 0 on EOF. + // Start reading new command, return the number of arguments, leaving mBuffer positioned at the + // beginning of first argument. Return 0 on EOF. template<class FailFn> int getCount(FailFn fail_fn) { mLinesLeft = 1; @@ -451,11 +452,14 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( (CREATE_ERROR("Write unexpectedly returned short: %d < 5", res)); } } - // Clear buffer and get count from next command. - n_buffer->clear(); for (;;) { + // Clear buffer and get count from next command. + n_buffer->clear(); // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect. int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */)); + if (poll_res < 0) { + fail_fn_z(CREATE_ERROR("Poll failed: %d: %s", errno, strerror(errno))); + } if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) { if (n_buffer->getCount(fail_fn_z) != 0) { break; 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/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index f8546b73a37e..596cfe5a695d 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2515,6 +2515,10 @@ <attr name="versionMajor" format="integer" /> <!-- The SHA-256 digest of the SDK library signing certificate. --> <attr name="certDigest" format="string" /> + <!-- Specify whether the SDK is optional. The default is false, false means app can be + installed even if the SDK library doesn't exist, and the SDK library can be uninstalled + when the app is still installed. --> + <attr name="optional" format="boolean" /> </declare-styleable> <!-- The <code>static-library</code> tag declares that this apk is providing itself diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 387a1083c6d6..bf8e55fd986f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6856,4 +6856,7 @@ <!-- Whether the View-based scroll haptic feedback implementation is enabled for {@link InputDevice#SOURCE_ROTARY_ENCODER}s. --> <bool name="config_viewBasedRotaryEncoderHapticsEnabled">false</bool> + + <!-- Whether the media player is shown on the quick settings --> + <bool name="config_quickSettingsShowMediaPlayer">true</bool> </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index b12f30242e80..f10e7f8c337e 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -115,6 +115,8 @@ <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @hide @SystemApi --> <public name="isVirtualDeviceOnly"/> + <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") --> + <public name="optional"/> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1f6ac80a133c..4596ca74bf8f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5252,6 +5252,8 @@ <!-- Zen mode - name of default automatic calendar time-based rule that is triggered every night (when sleeping). [CHAR LIMIT=40] --> <string name="zen_mode_default_every_night_name">Sleeping</string> + <!-- Zen mode - Trigger description of the rule, indicating which app owns it. [CHAR_LIMIT=100] --> + <string name="zen_mode_implicit_trigger_description">Managed by <xliff:g id="app_name">%1$s</xliff:g></string> <!-- Zen mode - Condition summary when a rule is activated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] --> <string name="zen_mode_implicit_activated">On</string> <!-- Zen mode - Condition summary when a rule is deactivated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a5b102836816..fd6158d02b8f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2586,6 +2586,7 @@ <java-symbol type="string" name="zen_mode_default_weekends_name" /> <java-symbol type="string" name="zen_mode_default_events_name" /> <java-symbol type="string" name="zen_mode_default_every_night_name" /> + <java-symbol type="string" name="zen_mode_implicit_trigger_description" /> <java-symbol type="string" name="zen_mode_implicit_activated" /> <java-symbol type="string" name="zen_mode_implicit_deactivated" /> <java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" /> @@ -5298,4 +5299,6 @@ <java-symbol type="array" name="config_tvExternalInputLoggingDeviceBrandNames" /> <java-symbol type="bool" name="config_viewRotaryEncoderHapticScrollFedbackEnabled" /> <java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" /> + + <java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" /> </resources> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 88abaa179ef0..1a3ec27418a6 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -7,19 +7,35 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "FrameworksCoreTests", - +filegroup { + name: "FrameworksCoreTests-aidl", srcs: [ - "src/**/*.java", - "src/**/*.kt", "src/**/I*.aidl", + "aidl/**/I*.aidl", + ], + visibility: ["//visibility:private"], +} + +filegroup { + name: "FrameworksCoreTests-helpers", + srcs: [ "DisabledTestApp/src/**/*.java", "EnabledTestApp/src/**/*.java", "BinderProxyCountingTestApp/src/**/*.java", "BinderProxyCountingTestService/src/**/*.java", "BinderDeathRecipientHelperApp/src/**/*.java", - "aidl/**/I*.aidl", + ], + visibility: ["//visibility:private"], +} + +android_test { + name: "FrameworksCoreTests", + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ":FrameworksCoreTests-aidl", + ":FrameworksCoreTests-helpers", ":FrameworksCoreTestDoubles-sources", ], @@ -177,16 +193,29 @@ android_ravenwood_test { "androidx.annotation_annotation", "androidx.test.rules", "androidx.test.ext.junit", + "androidx.test.uiautomator_uiautomator", + "compatibility-device-util-axt", + "flag-junit", "mockito_ravenwood", "platform-test-annotations", "flag-junit", + "testng", ], srcs: [ - "src/android/os/BuildTest.java", - "src/android/os/FileUtilsTest.java", + "src/android/os/**/*.java", + "src/com/android/internal/os/**/*.java", "src/android/util/**/*.java", + "src/com/android/internal/os/LongArrayMultiStateCounterTest.java", "src/com/android/internal/util/**/*.java", - "testdoubles/src/com/android/internal/util/**/*.java", + "src/com/android/internal/power/EnergyConsumerStatsTest.java", + ":FrameworksCoreTests{.aapt.srcjar}", + ":FrameworksCoreTests-aidl", + ":FrameworksCoreTests-helpers", + ":FrameworksCoreTestDoubles-sources", ], + aidl: { + generate_get_transaction_name: true, + local_include_dirs: ["aidl"], + }, auto_gen_config: true, } diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java index ba2ea88e8e01..d629f6a8c57c 100644 --- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java +++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java @@ -19,15 +19,20 @@ package android.app; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; +import static org.junit.Assert.assertThrows; + import android.content.ComponentName; import android.net.Uri; import android.os.Parcel; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.google.common.base.Strings; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,6 +43,9 @@ import java.lang.reflect.Field; public class AutomaticZenRuleTest { private static final String CLASS = "android.app.AutomaticZenRule"; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void testLongFields_inConstructor() { String longString = Strings.repeat("A", 65536); @@ -100,6 +108,7 @@ public class AutomaticZenRuleTest { } @Test + @EnableFlags(Flags.FLAG_MODES_API) public void testLongInputsFromParcel() { // Create a rule with long fields, set directly via reflection so that we can confirm that // a rule with too-long fields that comes in via a parcel has its fields truncated directly. @@ -152,6 +161,60 @@ public class AutomaticZenRuleTest { fromParcel.getOwner().getPackageName().length()); assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, fromParcel.getOwner().getClassName().length()); - assertEquals(AutomaticZenRule.MAX_DESC_LENGTH, rule.getTriggerDescription().length()); + assertEquals(AutomaticZenRule.MAX_DESC_LENGTH, fromParcel.getTriggerDescription().length()); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void validate_builderWithValidType_succeeds() throws Exception { + AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")) + .setType(AutomaticZenRule.TYPE_BEDTIME) + .build(); + rule.validate(); // No exception. + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void validate_builderWithoutType_succeeds() throws Exception { + AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build(); + rule.validate(); // No exception. + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void validate_constructorWithoutType_succeeds() throws Exception { + AutomaticZenRule rule = new AutomaticZenRule("rule", new ComponentName("pkg", "cps"), + new ComponentName("pkg", "activity"), Uri.parse("condition"), null, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + rule.validate(); // No exception. + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void validate_invalidType_throws() throws Exception { + AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build(); + + // Set the field via reflection. + Field typeField = AutomaticZenRule.class.getDeclaredField("mType"); + typeField.setAccessible(true); + typeField.set(rule, 100); + + assertThrows(IllegalArgumentException.class, rule::validate); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void setType_invalidType_throws() { + AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build(); + + assertThrows(IllegalArgumentException.class, () -> rule.setType(100)); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void setTypeBuilder_invalidType_throws() { + AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("rule", Uri.parse("uri")); + + assertThrows(IllegalArgumentException.class, () -> builder.setType(100)); } } diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index 3768063f2a91..cd6abddc20a1 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -20,9 +20,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.SmallTest; import org.junit.After; +import org.junit.Rule; import org.junit.Test; /** @@ -35,7 +39,10 @@ import org.junit.Test; * atest FrameworksCoreTests:PropertyInvalidatedCacheTests */ @SmallTest +@IgnoreUnderRavenwood(blockedBy = PropertyInvalidatedCache.class) public class PropertyInvalidatedCacheTests { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); // Configuration for creating caches private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST; diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java index d0c3470c4c1f..006828f12156 100644 --- a/core/tests/coretests/src/android/os/AidlTest.java +++ b/core/tests/coretests/src/android/os/AidlTest.java @@ -16,23 +16,36 @@ package android.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.SmallTest; import com.google.android.collect.Lists; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; import java.util.List; -public class AidlTest extends TestCase { +@IgnoreUnderRavenwood(blockedBy = Parcel.class) +public class AidlTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private IAidlTest mRemote; private AidlObject mLocal; private NonAutoGeneratedObject mNonAutoGenerated; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { mLocal = new AidlObject(); mRemote = IAidlTest.Stub.asInterface(mLocal); mNonAutoGenerated = new NonAutoGeneratedObject("NonAutoGeneratedObject"); @@ -212,12 +225,14 @@ public class AidlTest extends TestCase { } } + @Test @SmallTest public void testInt() throws Exception { int result = mRemote.intMethod(42); assertEquals(42, result); } + @Test @SmallTest public void testParcelableIn() throws Exception { TestParcelable arg = new TestParcelable(43, "hi"); @@ -228,6 +243,7 @@ public class AidlTest extends TestCase { assertEquals(44, result.mAnInt); } + @Test @SmallTest public void testParcelableOut() throws Exception { TestParcelable arg = new TestParcelable(43, "hi"); @@ -236,6 +252,7 @@ public class AidlTest extends TestCase { assertEquals(44, arg.mAnInt); } + @Test @SmallTest public void testParcelableInOut() throws Exception { TestParcelable arg = new TestParcelable(43, "hi"); @@ -244,6 +261,7 @@ public class AidlTest extends TestCase { assertEquals(44, arg.mAnInt); } + @Test @SmallTest public void testListParcelableLonger() throws Exception { List<TestParcelable> list = Lists.newArrayList(); @@ -268,6 +286,7 @@ public class AidlTest extends TestCase { assertNotSame(list.get(1), list.get(2)); } + @Test @SmallTest public void testListParcelableShorter() throws Exception { List<TestParcelable> list = Lists.newArrayList(); @@ -290,6 +309,7 @@ public class AidlTest extends TestCase { assertNotSame(list.get(0), list.get(1)); } + @Test @SmallTest public void testArrays() throws Exception { // boolean @@ -363,14 +383,14 @@ public class AidlTest extends TestCase { float[] fr = mRemote.floatArray(f0, f1, f2); assertEquals(1, fr.length); - assertEquals(90.1f, fr[0]); + assertEquals(90.1f, fr[0], 0.0f); - assertEquals(90.1f, f1[0]); + assertEquals(90.1f, f1[0], 0.0f); assertEquals(0f, f1[1], 0.0f); - assertEquals(90.1f, f2[0]); - assertEquals(90.5f, f2[1]); - assertEquals(90.6f, f2[2]); + assertEquals(90.1f, f2[0], 0.0f); + assertEquals(90.5f, f2[1], 0.0f); + assertEquals(90.6f, f2[2], 0.0f); // double double[] d0 = new double[]{100.1}; @@ -379,14 +399,14 @@ public class AidlTest extends TestCase { double[] dr = mRemote.doubleArray(d0, d1, d2); assertEquals(1, dr.length); - assertEquals(100.1, dr[0]); + assertEquals(100.1, dr[0], 0.0); - assertEquals(100.1, d1[0]); + assertEquals(100.1, d1[0], 0.0); assertEquals(0, d1[1], 0.0); - assertEquals(100.1, d2[0]); - assertEquals(100.5, d2[1]); - assertEquals(100.6, d2[2]); + assertEquals(100.1, d2[0], 0.0); + assertEquals(100.5, d2[1], 0.0); + assertEquals(100.6, d2[2], 0.0); // String String[] s0 = new String[]{"s0[0]"}; @@ -405,6 +425,7 @@ public class AidlTest extends TestCase { assertEquals("s2[2]", s2[2]); } + @Test @SmallTest public void testVoidSecurityException() throws Exception { boolean good = false; @@ -416,6 +437,7 @@ public class AidlTest extends TestCase { assertEquals(good, true); } + @Test @SmallTest public void testIntSecurityException() throws Exception { boolean good = false; @@ -427,6 +449,7 @@ public class AidlTest extends TestCase { assertEquals(good, true); } + @Test @SmallTest public void testGetTransactionNameAutoGenerated() throws Exception { assertEquals(15, mLocal.getMaxTransactionId()); @@ -446,6 +469,7 @@ public class AidlTest extends TestCase { mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_parcelableIn)); } + @Test @SmallTest public void testGetTransactionNameNonAutoGenerated() throws Exception { assertEquals(0, mNonAutoGenerated.getMaxTransactionId()); diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java index 2cce43f70774..eff52f0dc4e2 100644 --- a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java +++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java @@ -25,6 +25,8 @@ import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -36,6 +38,7 @@ import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,6 +55,7 @@ import java.util.concurrent.atomic.AtomicReference; * Tests functionality of {@link android.os.IBinder.DeathRecipient} callbacks. */ @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = ActivityManager.class) public class BinderDeathRecipientTest { private static final String TAG = BinderDeathRecipientTest.class.getSimpleName(); private static final String TEST_PACKAGE_NAME_1 = @@ -59,6 +63,9 @@ public class BinderDeathRecipientTest { private static final String TEST_PACKAGE_NAME_2 = "com.android.frameworks.coretests.bdr_helper_app2"; + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private Context mContext; private Handler mHandler; private ActivityManager mActivityManager; diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java index 2089c6cf89c9..bcd9521019e4 100644 --- a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java +++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java @@ -24,6 +24,8 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -35,7 +37,8 @@ import com.android.frameworks.coretests.aidl.IBpcCallbackObserver; import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService; import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService; -import org.junit.BeforeClass; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -71,6 +74,7 @@ import java.util.function.Consumer; */ @LargeTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = ActivityManager.class) public class BinderProxyCountingTest { private static final String TAG = BinderProxyCountingTest.class.getSimpleName(); @@ -107,11 +111,14 @@ public class BinderProxyCountingTest { }; private static int sTestPkgUid; + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + /** * Setup any common data for the upcoming tests. */ - @BeforeClass - public static void setUpOnce() throws Exception { + @Before + public void setUp() throws Exception { sContext = InstrumentationRegistry.getContext(); sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0); ((ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid(sTestPkgUid, diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java index 3567d17ea874..a903ed91cb3d 100644 --- a/core/tests/coretests/src/android/os/BinderProxyTest.java +++ b/core/tests/coretests/src/android/os/BinderProxyTest.java @@ -16,19 +16,34 @@ package android.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.test.AndroidTestCase; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -public class BinderProxyTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = PowerManager.class) +public class BinderProxyTest { private static class CountingListener implements Binder.ProxyTransactListener { int mStartedCount; int mEndedCount; @@ -43,17 +58,22 @@ public class BinderProxyTest extends AndroidTestCase { } }; + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + private Context mContext; private PowerManager mPowerManager; /** * Setup any common data for the upcoming tests. */ - @Override + @Before public void setUp() throws Exception { - super.setUp(); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); } + @Test @MediumTest public void testNoListener() throws Exception { CountingListener listener = new CountingListener(); @@ -66,6 +86,7 @@ public class BinderProxyTest extends AndroidTestCase { assertEquals(0, listener.mEndedCount); } + @Test @MediumTest public void testListener() throws Exception { CountingListener listener = new CountingListener(); @@ -77,6 +98,7 @@ public class BinderProxyTest extends AndroidTestCase { assertEquals(1, listener.mEndedCount); } + @Test @MediumTest public void testSessionPropagated() throws Exception { Binder.setProxyTransactListener(new Binder.ProxyTransactListener() { @@ -95,6 +117,7 @@ public class BinderProxyTest extends AndroidTestCase { private IBinder mRemoteBinder = null; + @Test @MediumTest public void testGetExtension() throws Exception { final CountDownLatch bindLatch = new CountDownLatch(1); diff --git a/core/tests/coretests/src/android/os/BinderTest.java b/core/tests/coretests/src/android/os/BinderTest.java index 02f87901318d..6c8b69fc9c5c 100644 --- a/core/tests/coretests/src/android/os/BinderTest.java +++ b/core/tests/coretests/src/android/os/BinderTest.java @@ -16,21 +16,35 @@ package android.os; -import androidx.test.filters.SmallTest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.testng.Assert.assertThrows; -import junit.framework.TestCase; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; -import static org.testng.Assert.assertThrows; +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; -public class BinderTest extends TestCase { +@IgnoreUnderRavenwood(blockedBy = WorkSource.class) +public class BinderTest { private static final int UID = 100; + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test @SmallTest public void testSetWorkSource() throws Exception { Binder.setCallingWorkSourceUid(UID); assertEquals(UID, Binder.getCallingWorkSourceUid()); } + @Test @SmallTest public void testClearWorkSource() throws Exception { Binder.setCallingWorkSourceUid(UID); @@ -38,6 +52,7 @@ public class BinderTest extends TestCase { assertEquals(-1, Binder.getCallingWorkSourceUid()); } + @Test @SmallTest public void testRestoreWorkSource() throws Exception { Binder.setCallingWorkSourceUid(UID); @@ -46,11 +61,13 @@ public class BinderTest extends TestCase { assertEquals(UID, Binder.getCallingWorkSourceUid()); } + @Test @SmallTest public void testGetCallingUidOrThrow_throws() throws Exception { assertThrows(IllegalStateException.class, () -> Binder.getCallingUidOrThrow()); } + @Test @SmallTest public void testGetExtension() throws Exception { Binder binder = new Binder(); diff --git a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java index 48c9df65da61..4172bffe100c 100644 --- a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java +++ b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java @@ -16,21 +16,42 @@ package android.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.test.AndroidTestCase; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.Log; +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +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; /** * Test whether Binder calls inherit thread priorities correctly. */ -public class BinderThreadPriorityTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = ActivityManager.class) +public class BinderThreadPriorityTest { private static final String TAG = "BinderThreadPriorityTest"; + + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + private Context mContext; private IBinderThreadPriorityService mService; private int mSavedPriority; @@ -55,12 +76,11 @@ public class BinderThreadPriorityTest extends AndroidTestCase { private static void fail() { throw new RuntimeException("unimplemented"); } } - @Override - protected void setUp() throws Exception { - super.setUp(); - - getContext().bindService( - new Intent(getContext(), BinderThreadPriorityService.class), + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mContext.bindService( + new Intent(mContext, BinderThreadPriorityService.class), mConnection, Context.BIND_AUTO_CREATE); synchronized (this) { @@ -80,8 +100,8 @@ public class BinderThreadPriorityTest extends AndroidTestCase { Log.i(TAG, "Saved priority: " + mSavedPriority); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { // HACK -- see bug 2665914 -- setThreadPriority() doesn't always set the // scheduler group reliably unless we start out with background priority. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); @@ -89,8 +109,7 @@ public class BinderThreadPriorityTest extends AndroidTestCase { assertEquals(mSavedPriority, Process.getThreadPriority(Process.myTid())); assertEquals(expectedSchedulerGroup(mSavedPriority), getSchedulerGroup()); - getContext().unbindService(mConnection); - super.tearDown(); + mContext.unbindService(mConnection); } public static String getSchedulerGroup() { @@ -111,6 +130,7 @@ public class BinderThreadPriorityTest extends AndroidTestCase { return "/"; } + @Test public void testPassPriorityToService() throws Exception { for (int prio = 19; prio >= -20; prio--) { Process.setThreadPriority(prio); @@ -125,6 +145,7 @@ public class BinderThreadPriorityTest extends AndroidTestCase { } } + @Test public void testCallBackFromServiceWithPriority() throws Exception { for (int prio = -20; prio <= 19; prio++) { final int expected = prio; diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java index b14c88f7341e..552066c92931 100644 --- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java +++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java @@ -19,11 +19,14 @@ package android.os; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; @@ -33,6 +36,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,12 +46,16 @@ import org.junit.runner.RunWith; @LargeTest @Presubmit @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = ActivityManager.class) public class BinderWorkSourceTest { private static Context sContext; private static final int UID = 100; private static final int SECOND_UID = 200; private static final int UID_NONE = ThreadLocalWorkSource.UID_NONE; + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private IBinderWorkSourceService mService; private IBinderWorkSourceNestedService mNestedService; @@ -71,13 +79,9 @@ public class BinderWorkSourceTest { } }; - @BeforeClass - public static void setUpOnce() throws Exception { - sContext = InstrumentationRegistry.getContext(); - } - @Before public void setUp() throws Exception { + sContext = InstrumentationRegistry.getContext(); sContext.bindService( new Intent(sContext, BinderWorkSourceService.class), mConnection, Context.BIND_AUTO_CREATE); diff --git a/core/tests/coretests/src/android/os/BroadcasterTest.java b/core/tests/coretests/src/android/os/BroadcasterTest.java index b4c47af93355..7829457109cd 100644 --- a/core/tests/coretests/src/android/os/BroadcasterTest.java +++ b/core/tests/coretests/src/android/os/BroadcasterTest.java @@ -16,16 +16,26 @@ package android.os; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; -import junit.framework.TestCase; +@RunWith(AndroidJUnit4.class) +public class BroadcasterTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); -public class BroadcasterTest extends TestCase { private static final int MESSAGE_A = 23234; private static final int MESSAGE_B = 3; private static final int MESSAGE_C = 14; private static final int MESSAGE_D = 95; + @Test @MediumTest public void test1() throws Exception { /* @@ -103,6 +113,7 @@ public class BroadcasterTest extends TestCase { } } + @Test @MediumTest public void test2() throws Exception { /* @@ -112,6 +123,7 @@ public class BroadcasterTest extends TestCase { tester.doTest(1000); } + @Test @MediumTest public void test3() throws Exception { /* @@ -121,6 +133,7 @@ public class BroadcasterTest extends TestCase { tester.doTest(1000); } + @Test @MediumTest public void test4() throws Exception { /* @@ -156,6 +169,7 @@ public class BroadcasterTest extends TestCase { tester.doTest(1000); } + @Test @MediumTest public void test5() throws Exception { /* @@ -191,6 +205,7 @@ public class BroadcasterTest extends TestCase { tester.doTest(1000); } + @Test @MediumTest public void test6() throws Exception { /* diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java index a3bda8b23f30..e7b5dff60110 100644 --- a/core/tests/coretests/src/android/os/BundleTest.java +++ b/core/tests/coretests/src/android/os/BundleTest.java @@ -23,13 +23,16 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.Log; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,6 +48,9 @@ import java.util.Objects; @Presubmit @RunWith(AndroidJUnit4.class) public class BundleTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private Log.TerribleFailureHandler mWtfHandler; @After @@ -115,6 +121,7 @@ public class BundleTest { } @Test + @IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class) public void testCreateFromParcel() throws Exception { boolean withFd; Parcel p; @@ -295,6 +302,7 @@ public class BundleTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void kindofEquals_lazyValuesAndDifferentClassLoaders_returnsFalse() { Parcelable p1 = new CustomParcelable(13, "Tiramisu"); Parcelable p2 = new CustomParcelable(13, "Tiramisu"); @@ -350,6 +358,7 @@ public class BundleTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void readWriteLengthMismatch_logsWtf() throws Exception { mWtfHandler = Log.setWtfHandler((tag, e, system) -> { throw new RuntimeException(e); @@ -364,6 +373,7 @@ public class BundleTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void getParcelable_whenThrowingAndNotDefusing_throws() throws Exception { Bundle.setShouldDefuse(false); Bundle bundle = new Bundle(); @@ -376,6 +386,7 @@ public class BundleTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void getParcelable_whenThrowingAndDefusing_returnsNull() throws Exception { Bundle.setShouldDefuse(true); Bundle bundle = new Bundle(); @@ -391,6 +402,7 @@ public class BundleTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void getParcelable_whenThrowingAndDefusing_leavesElement() throws Exception { Bundle.setShouldDefuse(true); Bundle bundle = new Bundle(); diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java index 42c97f3876a6..c2cea0a2c286 100644 --- a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java +++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java @@ -22,6 +22,8 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.os.CancellationSignalBeamer.Receiver; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.PollingCheck; import android.util.PollingCheck.PollingCheckCondition; @@ -29,6 +31,8 @@ import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,11 +44,20 @@ import java.util.concurrent.CountDownLatch; @RunWith(AndroidJUnit4.class) @SmallTest +@IgnoreUnderRavenwood(blockedBy = CancellationSignalBeamer.class) public class CancellationSignalBeamerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); - private CancellationSignal mSenderSignal = new CancellationSignal(); + private CancellationSignal mSenderSignal; private CancellationSignal mReceivedSignal; - private Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private Context mContext; + + @Before + public void setUp() { + mSenderSignal = new CancellationSignal(); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + } @Test public void testBeam_null() { @@ -208,17 +221,22 @@ public class CancellationSignalBeamerTest { mReceivedSignal = mReceiver.unbeam(cancellationSignalToken); } - private final Sender mSender = new Sender() { - @Override - public void onCancel(IBinder token) { - mReceiver.cancel(token); - } + private Sender mSender; + private Receiver mReceiver; - @Override - public void onForget(IBinder token) { - mReceiver.forget(token); - } - }; + @Before + public void setUpSenderReceiver() { + mSender = new Sender() { + @Override + public void onCancel(IBinder token) { + mReceiver.cancel(token); + } - private final Receiver mReceiver = new Receiver(false); + @Override + public void onForget(IBinder token) { + mReceiver.forget(token); + } + }; + mReceiver = new Receiver(false); + } } diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java index ef38cdec0d63..1aa263f13d92 100644 --- a/core/tests/coretests/src/android/os/EnvironmentTest.java +++ b/core/tests/coretests/src/android/os/EnvironmentTest.java @@ -28,12 +28,15 @@ import static org.junit.Assert.assertEquals; import android.content.Context; import android.os.storage.StorageManager; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,7 +46,11 @@ import java.util.UUID; import java.util.function.BiFunction; @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = Environment.class) public class EnvironmentTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private File dir; private static Context getContext() { diff --git a/core/tests/coretests/src/android/os/FileBridgeTest.java b/core/tests/coretests/src/android/os/FileBridgeTest.java index 708bfa6ece2e..726670b4d625 100644 --- a/core/tests/coretests/src/android/os/FileBridgeTest.java +++ b/core/tests/coretests/src/android/os/FileBridgeTest.java @@ -19,12 +19,25 @@ package android.os; import static android.os.ParcelFileDescriptor.MODE_CREATE; import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import android.os.FileBridge.FileBridgeOutputStream; -import android.test.AndroidTestCase; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.test.MoreAsserts; +import androidx.test.ext.junit.runners.AndroidJUnit4; + import libcore.io.Streams; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -32,18 +45,20 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Random; -public class FileBridgeTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = ParcelFileDescriptor.class) +public class FileBridgeTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private File file; private ParcelFileDescriptor outputFile; private FileBridge bridge; private FileBridgeOutputStream client; - @Override - protected void setUp() throws Exception { - super.setUp(); - - file = getContext().getFileStreamPath("meow.dat"); + @Before + public void setUp() throws Exception { + file = File.createTempFile("meow", "dat"); file.delete(); outputFile = ParcelFileDescriptor.open(file, MODE_CREATE | MODE_READ_WRITE); @@ -54,8 +69,8 @@ public class FileBridgeTest extends AndroidTestCase { client = new FileBridgeOutputStream(bridge.getClientSocket()); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { outputFile.close(); file.delete(); } @@ -76,17 +91,20 @@ public class FileBridgeTest extends AndroidTestCase { MoreAsserts.assertEquals(expected, Streams.readFully(new FileInputStream(file))); } + @Test public void testNoWriteNoSync() throws Exception { assertOpen(); closeAndAssertClosed(); } + @Test public void testNoWriteSync() throws Exception { assertOpen(); client.flush(); closeAndAssertClosed(); } + @Test public void testWriteNoSync() throws Exception { assertOpen(); client.write("meow".getBytes(StandardCharsets.UTF_8)); @@ -94,6 +112,7 @@ public class FileBridgeTest extends AndroidTestCase { assertContents("meow".getBytes(StandardCharsets.UTF_8)); } + @Test public void testWriteSync() throws Exception { assertOpen(); client.write("cake".getBytes(StandardCharsets.UTF_8)); @@ -102,6 +121,7 @@ public class FileBridgeTest extends AndroidTestCase { assertContents("cake".getBytes(StandardCharsets.UTF_8)); } + @Test public void testWriteSyncWrite() throws Exception { assertOpen(); client.write("meow".getBytes(StandardCharsets.UTF_8)); @@ -111,6 +131,7 @@ public class FileBridgeTest extends AndroidTestCase { assertContents("meowcake".getBytes(StandardCharsets.UTF_8)); } + @Test public void testEmptyWrite() throws Exception { assertOpen(); client.write(new byte[0]); @@ -118,6 +139,7 @@ public class FileBridgeTest extends AndroidTestCase { assertContents(new byte[0]); } + @Test public void testWriteAfterClose() throws Exception { assertOpen(); client.write("meow".getBytes(StandardCharsets.UTF_8)); @@ -130,6 +152,7 @@ public class FileBridgeTest extends AndroidTestCase { assertContents("meow".getBytes(StandardCharsets.UTF_8)); } + @Test public void testRandomWrite() throws Exception { final Random r = new Random(); final ByteArrayOutputStream result = new ByteArrayOutputStream(); @@ -146,6 +169,7 @@ public class FileBridgeTest extends AndroidTestCase { assertContents(result.toByteArray()); } + @Test public void testGiantWrite() throws Exception { final byte[] test = new byte[263401]; new Random().nextBytes(test); diff --git a/core/tests/coretests/src/android/os/FileObserverTest.java b/core/tests/coretests/src/android/os/FileObserverTest.java index ece7645b7389..3cd8045c32cb 100644 --- a/core/tests/coretests/src/android/os/FileObserverTest.java +++ b/core/tests/coretests/src/android/os/FileObserverTest.java @@ -16,21 +16,37 @@ package android.os; -import android.test.AndroidTestCase; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.Log; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.File; import java.io.FileOutputStream; import java.util.Iterator; import java.util.List; import java.util.Map; -public class FileObserverTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = FileObserver.class) +public class FileObserverTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private Observer mObserver; private File mTestFile; @@ -57,18 +73,19 @@ public class FileObserverTest extends AndroidTestCase { } } - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { mTestFile = File.createTempFile(".file_observer_test", ".txt"); } - @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { if (mTestFile != null && mTestFile.exists()) { mTestFile.delete(); } } + @Test @MediumTest public void testRun() throws Exception { // make file changes and wait for them diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java index 93cfc40d555e..0bac1c728f3b 100644 --- a/core/tests/coretests/src/android/os/HandlerThreadTest.java +++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java @@ -16,18 +16,35 @@ package android.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; -public class HandlerThreadTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class HandlerThreadTest { private static final int TEST_WHAT = 1; + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private boolean mGotMessage = false; private int mGotMessageWhat = -1; private volatile boolean mDidSetup = false; private volatile int mLooperTid = -1; - + + @Test @MediumTest public void testHandlerThread() throws Exception { HandlerThread th1 = new HandlerThread("HandlerThreadTest") { diff --git a/core/tests/coretests/src/android/os/IdleHandlerTest.java b/core/tests/coretests/src/android/os/IdleHandlerTest.java index d8886c97838e..864466329389 100644 --- a/core/tests/coretests/src/android/os/IdleHandlerTest.java +++ b/core/tests/coretests/src/android/os/IdleHandlerTest.java @@ -17,12 +17,19 @@ package android.os; import android.os.MessageQueue.IdleHandler; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; -public class IdleHandlerTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class IdleHandlerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private static class BaseTestHandler extends TestHandlerThread { Handler mHandler; @@ -54,6 +61,7 @@ public class IdleHandlerTest extends TestCase { } } + @Test @MediumTest public void testOneShotFirst() throws Exception { TestHandlerThread tester = new BaseTestHandler() { @@ -88,6 +96,7 @@ public class IdleHandlerTest extends TestCase { tester.doTest(1000); } + @Test @MediumTest public void testOneShotLater() throws Exception { TestHandlerThread tester = new BaseTestHandler() { @@ -125,6 +134,7 @@ public class IdleHandlerTest extends TestCase { } + @Test @MediumTest public void testRepeatedFirst() throws Exception { TestHandlerThread tester = new BaseTestHandler() { @@ -159,6 +169,7 @@ public class IdleHandlerTest extends TestCase { tester.doTest(1000); } + @Test @MediumTest public void testRepeatedLater() throws Exception { TestHandlerThread tester = new BaseTestHandler() { diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java index 34712ce54e0f..b03fd6485786 100644 --- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java +++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java @@ -17,12 +17,14 @@ package android.os; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import org.junit.After; +import org.junit.Rule; import org.junit.Test; /** @@ -35,7 +37,10 @@ import org.junit.Test; * atest FrameworksCoreTests:IpcDataCacheTest */ @SmallTest +@IgnoreUnderRavenwood(blockedBy = IpcDataCache.class) public class IpcDataCacheTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); // Configuration for creating caches private static final String MODULE = IpcDataCache.MODULE_TEST; diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java index 88fc8267f5b2..0025e3a3c454 100644 --- a/core/tests/coretests/src/android/os/LocaleListTest.java +++ b/core/tests/coretests/src/android/os/LocaleListTest.java @@ -16,13 +16,29 @@ package android.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; import java.util.Locale; -public class LocaleListTest extends TestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = LocaleList.class) +public class LocaleListTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test @SmallTest public void testConstructor() throws Exception { LocaleList ll; @@ -51,6 +67,7 @@ public class LocaleListTest extends TestCase { assertEquals("fr,de", ll.toLanguageTags()); } + @Test @SmallTest public void testConstructor_nullThrows() throws Exception { try { @@ -61,6 +78,7 @@ public class LocaleListTest extends TestCase { } } + @Test @SmallTest public void testGetDefault_localeSetDefaultCalledButNoChangeNecessary() throws Exception { final Locale originalLocale = Locale.getDefault(); @@ -82,6 +100,7 @@ public class LocaleListTest extends TestCase { LocaleList.setDefault(originalLocaleList, originalLocaleIndex); } + @Test @SmallTest public void testIntersection() { LocaleList localesWithN = new LocaleList( diff --git a/core/tests/coretests/src/android/os/MemoryFileTest.java b/core/tests/coretests/src/android/os/MemoryFileTest.java index 05c2995fa158..a69542479afc 100644 --- a/core/tests/coretests/src/android/os/MemoryFileTest.java +++ b/core/tests/coretests/src/android/os/MemoryFileTest.java @@ -16,11 +16,21 @@ package android.os; -import android.test.AndroidTestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import androidx.test.filters.SmallTest; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -29,7 +39,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class MemoryFileTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = MemoryFile.class) +public class MemoryFileTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private void compareBuffers(byte[] buffer1, byte[] buffer2, int length) throws Exception { for (int i = 0; i < length; i++) { @@ -44,6 +58,8 @@ public class MemoryFileTest extends AndroidTestCase { */ // Flaky test - temporarily suppress from large suite for now // @LargeTest + @Test + @Ignore("Flaky test") public void testPurge() throws Exception { List<MemoryFile> files = new ArrayList<MemoryFile>(); try { @@ -70,6 +86,7 @@ public class MemoryFileTest extends AndroidTestCase { } } + @Test @SmallTest public void testRun() throws Exception { MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); @@ -102,6 +119,7 @@ public class MemoryFileTest extends AndroidTestCase { } // http://code.google.com/p/android/issues/detail?id=11415 + @Test public void testOutputStreamAdvances() throws IOException { MemoryFile file = new MemoryFile("MemoryFileTest", 10); @@ -142,24 +160,28 @@ public class MemoryFileTest extends AndroidTestCase { } } + @Test @SmallTest public void testReadNegativeOffset() throws Exception { readIndexOutOfBoundsException(-1, 5, "read() with negative offset should throw IndexOutOfBoundsException"); } + @Test @SmallTest public void testReadNegativeCount() throws Exception { readIndexOutOfBoundsException(5, -1, "read() with negative length should throw IndexOutOfBoundsException"); } + @Test @SmallTest public void testReadOffsetOverflow() throws Exception { readIndexOutOfBoundsException(testString.length + 10, 5, "read() with offset outside buffer should throw IndexOutOfBoundsException"); } + @Test @SmallTest public void testReadOffsetCountOverflow() throws Exception { readIndexOutOfBoundsException(testString.length, 11, @@ -167,6 +189,7 @@ public class MemoryFileTest extends AndroidTestCase { } // Test behavior of read() at end of file + @Test @SmallTest public void testReadEOF() throws Exception { MemoryFile file = new MemoryFile("MemoryFileTest", testString.length); @@ -189,6 +212,7 @@ public class MemoryFileTest extends AndroidTestCase { } // Tests that close() is idempotent + @Test @SmallTest public void testCloseClose() throws Exception { MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); @@ -199,6 +223,7 @@ public class MemoryFileTest extends AndroidTestCase { } // Tests that we can't read from a closed memory file + @Test @SmallTest public void testCloseRead() throws Exception { MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); @@ -214,6 +239,7 @@ public class MemoryFileTest extends AndroidTestCase { } // Tests that we can't write to a closed memory file + @Test @SmallTest public void testCloseWrite() throws Exception { MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); @@ -229,6 +255,7 @@ public class MemoryFileTest extends AndroidTestCase { } // Tests that we can't call allowPurging() after close() + @Test @SmallTest public void testCloseAllowPurging() throws Exception { MemoryFile file = new MemoryFile("MemoryFileTest", 1000000); @@ -245,6 +272,7 @@ public class MemoryFileTest extends AndroidTestCase { } // Tests that we don't leak file descriptors or mmap areas + @Test @LargeTest public void testCloseLeak() throws Exception { // open enough memory files that we should run out of diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java index 2c5588e6ed0d..851e61259241 100644 --- a/core/tests/coretests/src/android/os/MessageQueueTest.java +++ b/core/tests/coretests/src/android/os/MessageQueueTest.java @@ -16,13 +16,22 @@ package android.os; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.MediumTest; import androidx.test.filters.Suppress; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; @Suppress // Failing. -public class MessageQueueTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class MessageQueueTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private static class BaseTestHandler extends TestHandlerThread { Handler mHandler; @@ -61,6 +70,7 @@ public class MessageQueueTest extends TestCase { } } + @Test @MediumTest public void testMessageOrder() throws Exception { TestHandlerThread tester = new BaseTestHandler() { @@ -80,6 +90,7 @@ public class MessageQueueTest extends TestCase { tester.doTest(1000); } + @Test @MediumTest public void testAtFrontOfQueue() throws Exception { TestHandlerThread tester = new BaseTestHandler() { @@ -141,7 +152,9 @@ public class MessageQueueTest extends TestCase { } } + @Test @MediumTest + @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700") public void testFieldIntegrity() throws Exception { TestHandlerThread tester = new TestFieldIntegrityHandler() { @@ -157,7 +170,7 @@ public class MessageQueueTest extends TestCase { public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 0) { - msg.flags = -1; + msg.flags = Message.FLAGS_TO_CLEAR_ON_COPY_FROM; msg.what = 1; msg.arg1 = 456; msg.arg2 = 789; diff --git a/core/tests/coretests/src/android/os/MessengerTest.java b/core/tests/coretests/src/android/os/MessengerTest.java index 9143ff1d1017..eb6263fe8053 100644 --- a/core/tests/coretests/src/android/os/MessengerTest.java +++ b/core/tests/coretests/src/android/os/MessengerTest.java @@ -16,15 +16,31 @@ package android.os; +import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.test.AndroidTestCase; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; -public class MessengerTest extends AndroidTestCase { +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = ActivityManager.class) +public class MessengerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + private Context mContext; private Messenger mServiceMessenger; private ServiceConnection mConnection = new ServiceConnection() { @@ -86,10 +102,10 @@ public class MessengerTest extends AndroidTestCase { } }; - @Override - protected void setUp() throws Exception { - super.setUp(); - getContext().bindService(new Intent(mContext, MessengerService.class), + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mContext.bindService(new Intent(mContext, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE); synchronized (this) { while (mServiceMessenger == null) { @@ -101,15 +117,14 @@ public class MessengerTest extends AndroidTestCase { } } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - getContext().unbindService(mConnection); + @After + public void tearDown() throws Exception { + mContext.unbindService(mConnection); } + @Test @MediumTest public void testSend() { (new TestThread()).doTest(1000); - } } diff --git a/core/tests/coretests/src/android/os/OsTests.java b/core/tests/coretests/src/android/os/OsTests.java deleted file mode 100644 index 08fb945857e9..000000000000 --- a/core/tests/coretests/src/android/os/OsTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os; - -import junit.framework.TestSuite; - -public class OsTests { - public static TestSuite suite() { - TestSuite suite = new TestSuite(OsTests.class.getName()); - - suite.addTestSuite(AidlTest.class); - suite.addTestSuite(BroadcasterTest.class); - suite.addTestSuite(FileObserverTest.class); - suite.addTestSuite(IdleHandlerTest.class); - suite.addTestSuite(MessageQueueTest.class); - suite.addTestSuite(MessengerTest.class); - suite.addTestSuite(PatternMatcherTest.class); - - return suite; - } -} diff --git a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java index b4e180cf647c..ffeab291e49f 100644 --- a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java +++ b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java @@ -20,11 +20,14 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArrayMap; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,6 +39,9 @@ import java.util.Map; @SmallTest @RunWith(AndroidJUnit4.class) public final class ParcelNullabilityTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Test public void nullByteArray() { Parcel p = Parcel.obtain(); @@ -61,6 +67,7 @@ public final class ParcelNullabilityTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void nullCharSequence() { Parcel p = Parcel.obtain(); p.writeCharSequence(null); @@ -69,6 +76,7 @@ public final class ParcelNullabilityTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void nullStrongBinder() { Parcel p = Parcel.obtain(); p.writeStrongBinder(null); @@ -77,6 +85,7 @@ public final class ParcelNullabilityTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void nullStringInterface() { Parcel p = Parcel.obtain(); p.writeStrongInterface(null); diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index 4b993fadc1e0..26f6d696768a 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -21,22 +21,29 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.runner.AndroidJUnit4; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @Presubmit @RunWith(AndroidJUnit4.class) public class ParcelTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private static final int WORK_SOURCE_1 = 1000; private static final int WORK_SOURCE_2 = 1002; private static final String INTERFACE_TOKEN_1 = "IBinder interface token"; private static final String INTERFACE_TOKEN_2 = "Another IBinder interface token"; @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testIsForRpc() { Parcel p = Parcel.obtain(); assertEquals(false, p.isForRpc()); @@ -44,6 +51,7 @@ public class ParcelTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCallingWorkSourceUidAfterWrite() { Parcel p = Parcel.obtain(); // Method does not throw if replaceCallingWorkSourceUid is called before requests headers @@ -64,6 +72,7 @@ public class ParcelTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCallingWorkSourceUidAfterEnforce() { Parcel p = Parcel.obtain(); p.writeInterfaceToken(INTERFACE_TOKEN_1); @@ -81,6 +90,7 @@ public class ParcelTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testParcelWithMultipleHeaders() { Parcel p = Parcel.obtain(); Binder.setCallingWorkSourceUid(WORK_SOURCE_1); @@ -138,6 +148,7 @@ public class ParcelTest { } @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testCompareDataInRange_whenSameDataWithBinder() { Binder binder = new Binder(); Parcel pA = Parcel.obtain(); @@ -297,6 +308,7 @@ public class ParcelTest { * and 1M length for complex objects are allowed. */ @Test + @IgnoreUnderRavenwood(blockedBy = Parcel.class) public void testAllocations_whenWithinLimit() { Binder.setIsDirectlyHandlingTransactionOverride(true); Parcel p = Parcel.obtain(); diff --git a/core/tests/coretests/src/android/os/PatternMatcherTest.java b/core/tests/coretests/src/android/os/PatternMatcherTest.java index 82350cd5ffdf..a5e036de6f20 100644 --- a/core/tests/coretests/src/android/os/PatternMatcherTest.java +++ b/core/tests/coretests/src/android/os/PatternMatcherTest.java @@ -16,9 +16,10 @@ package android.os; -import androidx.test.filters.SmallTest; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import junit.framework.TestCase; +import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,7 +27,7 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) @SmallTest -public class PatternMatcherTest extends TestCase{ +public class PatternMatcherTest { @Test public void testAdvancedPatternMatchesAnyToken() { diff --git a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java index 38ad90f11a23..46f1706c47c7 100644 --- a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java +++ b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java @@ -16,33 +16,48 @@ package android.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + import android.os.PerformanceCollector.PerformanceResultsWriter; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Random; -public class PerformanceCollectorTest extends TestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = PerformanceCollector.class) +public class PerformanceCollectorTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private PerformanceCollector mPerfCollector; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { mPerfCollector = new PerformanceCollector(); } - @Override - protected void tearDown() throws Exception { - super.tearDown(); + @After + public void tearDown() throws Exception { mPerfCollector = null; } + @Test @SmallTest public void testBeginSnapshotNoWriter() throws Exception { mPerfCollector.beginSnapshot("testBeginSnapshotNoWriter"); @@ -54,6 +69,7 @@ public class PerformanceCollectorTest extends TestCase { assertEquals(2, snapshot.size()); } + @Test @MediumTest public void testEndSnapshotNoWriter() throws Exception { mPerfCollector.beginSnapshot("testEndSnapshotNoWriter"); @@ -63,6 +79,7 @@ public class PerformanceCollectorTest extends TestCase { verifySnapshotBundle(snapshot); } + @Test @SmallTest public void testStartTimingNoWriter() throws Exception { mPerfCollector.startTiming("testStartTimingNoWriter"); @@ -74,6 +91,7 @@ public class PerformanceCollectorTest extends TestCase { verifyTimingBundle(measurement, new ArrayList<String>()); } + @Test @SmallTest public void testAddIterationNoWriter() throws Exception { mPerfCollector.startTiming("testAddIterationNoWriter"); @@ -83,6 +101,7 @@ public class PerformanceCollectorTest extends TestCase { verifyIterationBundle(iteration, "timing1"); } + @Test @SmallTest public void testStopTimingNoWriter() throws Exception { mPerfCollector.startTiming("testStopTimingNoWriter"); @@ -100,6 +119,7 @@ public class PerformanceCollectorTest extends TestCase { verifyTimingBundle(timing, labels); } + @Test @SmallTest public void testBeginSnapshot() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); @@ -114,6 +134,7 @@ public class PerformanceCollectorTest extends TestCase { assertEquals(2, snapshot.size()); } + @Test @MediumTest public void testEndSnapshot() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); @@ -127,6 +148,7 @@ public class PerformanceCollectorTest extends TestCase { verifySnapshotBundle(snapshot1); } + @Test @SmallTest public void testStartTiming() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); @@ -141,6 +163,7 @@ public class PerformanceCollectorTest extends TestCase { verifyTimingBundle(measurement, new ArrayList<String>()); } + @Test @SmallTest public void testAddIteration() throws Exception { mPerfCollector.startTiming("testAddIteration"); @@ -150,6 +173,7 @@ public class PerformanceCollectorTest extends TestCase { verifyIterationBundle(iteration, "timing5"); } + @Test @SmallTest public void testStopTiming() throws Exception { mPerfCollector.startTiming("testStopTiming"); @@ -167,6 +191,7 @@ public class PerformanceCollectorTest extends TestCase { verifyTimingBundle(timing, labels); } + @Test @SmallTest public void testAddMeasurementLong() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); @@ -188,6 +213,7 @@ public class PerformanceCollectorTest extends TestCase { assertEquals(-19354, results.getLong("testAddMeasurementLongNeg")); } + @Test @SmallTest public void testAddMeasurementFloat() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); @@ -202,13 +228,14 @@ public class PerformanceCollectorTest extends TestCase { Bundle results = writer.timingResults; assertEquals(4, results.size()); assertTrue(results.containsKey("testAddMeasurementFloatZero")); - assertEquals(0.0f, results.getFloat("testAddMeasurementFloatZero")); + assertEquals(0.0f, results.getFloat("testAddMeasurementFloatZero"), 0.0f); assertTrue(results.containsKey("testAddMeasurementFloatPos")); - assertEquals(348573.345f, results.getFloat("testAddMeasurementFloatPos")); + assertEquals(348573.345f, results.getFloat("testAddMeasurementFloatPos"), 0.0f); assertTrue(results.containsKey("testAddMeasurementFloatNeg")); - assertEquals(-19354.093f, results.getFloat("testAddMeasurementFloatNeg")); + assertEquals(-19354.093f, results.getFloat("testAddMeasurementFloatNeg"), 0.0f); } + @Test @SmallTest public void testAddMeasurementString() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); @@ -230,6 +257,7 @@ public class PerformanceCollectorTest extends TestCase { assertEquals("Hello World", results.getString("testAddMeasurementStringNonEmpty")); } + @Test @MediumTest public void testSimpleSequence() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); @@ -262,6 +290,7 @@ public class PerformanceCollectorTest extends TestCase { verifyTimingBundle(timing, labels); } + @Test @MediumTest public void testLongSequence() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); @@ -348,6 +377,7 @@ public class PerformanceCollectorTest extends TestCase { * Verify that snapshotting and timing do not interfere w/ each other, * by staggering calls to snapshot and timing functions. */ + @Test @MediumTest public void testOutOfOrderSequence() { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index 9b4dec4118a1..12a28446b0e1 100644 --- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java +++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java @@ -24,18 +24,25 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeNotNull; import android.os.PerformanceHintManager.Session; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = PerformanceHintManager.class) public class PerformanceHintManagerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private static final long RATE_1000 = 1000L; private static final long TARGET_166 = 166L; private static final long DEFAULT_TARGET_NS = 16666666L; diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java index 5d213caf61e6..cb281ff8a6a7 100644 --- a/core/tests/coretests/src/android/os/PowerManagerTest.java +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -17,7 +17,9 @@ package android.os; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; @@ -26,28 +28,34 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.os.Flags; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.test.AndroidTestCase; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -public class PowerManagerTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = PowerManager.class) +public class PowerManagerTest { private static final String TAG = "PowerManagerTest"; + private Context mContext; private PowerManager mPm; private UiDevice mUiDevice; private Executor mExec = Executors.newSingleThreadExecutor(); @@ -68,21 +76,27 @@ public class PowerManagerTest extends AndroidTestCase { String[] keys, String[] values); static { - System.loadLibrary("powermanagertest_jni"); + if (!RavenwoodRule.isUnderRavenwood()) { + System.loadLibrary("powermanagertest_jni"); + } } + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + // Required for RequiresFlagsEnabled and RequiresFlagsDisabled annotations to take effect. @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isUnderRavenwood() ? null + : DeviceFlagsValueProvider.createCheckFlagsRule(); /** * Setup any common data for the upcoming tests. */ - @Override + @Before public void setUp() throws Exception { - super.setUp(); MockitoAnnotations.initMocks(this); mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mUiDevice.executeShellCommand("cmd thermalservice override-status 0"); } @@ -100,6 +114,7 @@ public class PowerManagerTest extends AndroidTestCase { * * @throws Exception */ + @Test @SmallTest public void testPreconditions() throws Exception { assertNotNull(mPm); @@ -110,6 +125,7 @@ public class PowerManagerTest extends AndroidTestCase { * * @throws Exception */ + @Test @SmallTest public void testNewWakeLock() throws Exception { PowerManager.WakeLock wl = mPm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "FULL_WAKE_LOCK"); @@ -133,6 +149,7 @@ public class PowerManagerTest extends AndroidTestCase { * * @throws Exception */ + @Test @SmallTest public void testBadNewWakeLock() throws Exception { final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK @@ -152,6 +169,7 @@ public class PowerManagerTest extends AndroidTestCase { * * @throws Exception */ + @Test @SmallTest public void testWakeLockWithWorkChains() throws Exception { PowerManager.WakeLock wakeLock = mPm.newWakeLock( diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java index b2ffdc035e8b..310baa371163 100644 --- a/core/tests/coretests/src/android/os/ProcessTest.java +++ b/core/tests/coretests/src/android/os/ProcessTest.java @@ -17,12 +17,23 @@ package android.os; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -public class ProcessTest extends TestCase { +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import org.junit.Rule; +import org.junit.Test; + +@IgnoreUnderRavenwood(blockedBy = Process.class) +public class ProcessTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private static final int BAD_PID = 0; + @Test public void testProcessGetUidFromName() throws Exception { assertEquals(android.os.Process.SYSTEM_UID, Process.getUidForName("system")); assertEquals(Process.BLUETOOTH_UID, Process.getUidForName("bluetooth")); @@ -36,6 +47,7 @@ public class ProcessTest extends TestCase { Process.getUidForName("u3_a100")); } + @Test public void testProcessGetUidFromNameFailure() throws Exception { // Failure cases assertEquals(-1, Process.getUidForName("u2a_foo")); @@ -51,6 +63,7 @@ public class ProcessTest extends TestCase { * Tests getUidForPid() by ensuring that it returns the correct value when the process queried * doesn't exist. */ + @Test public void testGetUidForPidInvalidPid() { assertEquals(-1, Process.getUidForPid(BAD_PID)); } @@ -59,6 +72,7 @@ public class ProcessTest extends TestCase { * Tests getParentPid() by ensuring that it returns the correct value when the process queried * doesn't exist. */ + @Test public void testGetParentPidInvalidPid() { assertEquals(-1, Process.getParentPid(BAD_PID)); } @@ -67,11 +81,13 @@ public class ProcessTest extends TestCase { * Tests getThreadGroupLeader() by ensuring that it returns the correct value when the * thread queried doesn't exist. */ + @Test public void testGetThreadGroupLeaderInvalidTid() { // This function takes a TID instead of a PID but BAD_PID should also be a bad TID. assertEquals(-1, Process.getThreadGroupLeader(BAD_PID)); } + @Test public void testGetAdvertisedMem() { assertTrue(Process.getAdvertisedMem() > 0); assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem()); diff --git a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java index eff4826040f4..25ce240d1fb3 100644 --- a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java +++ b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java @@ -24,6 +24,8 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import android.content.Context; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.system.Os; import androidx.test.InstrumentationRegistry; @@ -31,6 +33,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,7 +44,11 @@ import java.io.FileOutputStream; import java.util.Arrays; @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = RedactingFileDescriptor.class) public class RedactingFileDescriptorTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private Context mContext; private File mFile; diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java index d07187c2c9d8..593833ec96a7 100644 --- a/core/tests/coretests/src/android/os/TraceTest.java +++ b/core/tests/coretests/src/android/os/TraceTest.java @@ -16,24 +16,36 @@ package android.os; -import android.test.AndroidTestCase; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.Log; import androidx.test.filters.LargeTest; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; /** * This class is used to test the native tracing support. Run this test * while tracing on the emulator and then run traceview to view the trace. */ -public class TraceTest extends AndroidTestCase -{ +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = Trace.class) +public class TraceTest { private static final String TAG = "TraceTest"; + + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private int eMethodCalls = 0; private int fMethodCalls = 0; private int gMethodCalls = 0; + @Test public void testNullStrings() { Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null); @@ -48,6 +60,7 @@ public class TraceTest extends AndroidTestCase Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, null); } + @Test @SmallTest public void testNativeTracingFromJava() { @@ -80,7 +93,8 @@ public class TraceTest extends AndroidTestCase native void nativeMethod(); native void nativeMethodAndStartTracing(); - + + @Test @LargeTest @Suppress // Failing. public void testMethodTracing() diff --git a/core/tests/coretests/src/android/os/VintfObjectTest.java b/core/tests/coretests/src/android/os/VintfObjectTest.java index ae6e79a040f7..f34b8fd358d9 100644 --- a/core/tests/coretests/src/android/os/VintfObjectTest.java +++ b/core/tests/coretests/src/android/os/VintfObjectTest.java @@ -16,12 +16,27 @@ package android.os; -import junit.framework.TestCase; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = VintfObject.class) +public class VintfObjectTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); -public class VintfObjectTest extends TestCase { /** * Quick check for {@link VintfObject#report VintfObject.report()}. */ + @Test public void testReport() { String[] xmls = VintfObject.report(); assertTrue(xmls.length > 0); diff --git a/core/tests/coretests/src/android/os/WorkSourceParcelTest.java b/core/tests/coretests/src/android/os/WorkSourceParcelTest.java index 483160687723..5f49186df0f5 100644 --- a/core/tests/coretests/src/android/os/WorkSourceParcelTest.java +++ b/core/tests/coretests/src/android/os/WorkSourceParcelTest.java @@ -16,17 +16,25 @@ package android.os; +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import static org.junit.Assert.assertEquals; - +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest +@IgnoreUnderRavenwood(reason = "JNI") public class WorkSourceParcelTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + /** * END_OF_PARCEL_MARKER is added at the end of Parcel on native or java side on write and * then read on java or native side on read. This way we can ensure that no extra data @@ -41,8 +49,11 @@ public class WorkSourceParcelTest { String[] names, int parcelEndMarker); static { - System.loadLibrary("worksourceparceltest_jni"); + if (!RavenwoodRule.isUnderRavenwood()) { + System.loadLibrary("worksourceparceltest_jni"); + } } + /** * Confirm that we can pass WorkSource from native to Java. */ diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java index 4206fd239528..85dc1277f14f 100644 --- a/core/tests/coretests/src/android/os/WorkSourceTest.java +++ b/core/tests/coretests/src/android/os/WorkSourceTest.java @@ -16,9 +16,19 @@ package android.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + import android.os.WorkSource.WorkChain; -import junit.framework.TestCase; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; @@ -28,7 +38,9 @@ import java.util.List; * * These tests will be moved to CTS when finalized. */ -public class WorkSourceTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class WorkSourceTest { + @Test public void testWorkChain_add() { WorkChain wc1 = new WorkChain(); wc1.addNode(56, null); @@ -46,6 +58,7 @@ public class WorkSourceTest extends TestCase { assertEquals(2, wc1.getSize()); } + @Test public void testWorkChain_equalsHashCode() { WorkChain wc1 = new WorkChain(); WorkChain wc2 = new WorkChain(); @@ -78,6 +91,7 @@ public class WorkSourceTest extends TestCase { assertFalse(wc1.hashCode() == wc2.hashCode()); } + @Test public void testWorkChain_constructor() { WorkChain wc1 = new WorkChain(); wc1.addNode(1, "foo") @@ -91,6 +105,7 @@ public class WorkSourceTest extends TestCase { assertFalse(wc1.equals(wc2)); } + @Test public void testDiff_workChains() { WorkSource ws1 = new WorkSource(); ws1.add(50); @@ -104,6 +119,7 @@ public class WorkSourceTest extends TestCase { assertFalse(ws2.diff(ws1)); } + @Test public void testEquals_workChains() { WorkSource ws1 = new WorkSource(); ws1.add(50); @@ -128,6 +144,7 @@ public class WorkSourceTest extends TestCase { assertFalse(ws3.equals(ws1)); } + @Test public void testEquals_workChains_nullEmptyAreEquivalent() { // Construct a WorkSource that has no WorkChains, but whose workChains list // is non-null. @@ -145,6 +162,7 @@ public class WorkSourceTest extends TestCase { assertFalse(ws1.equals(ws2)); } + @Test public void testWorkSourceParcelling() { WorkSource ws = new WorkSource(); @@ -164,6 +182,7 @@ public class WorkSourceTest extends TestCase { assertEquals(unparcelled, ws); } + @Test public void testSet_workChains() { WorkSource ws1 = new WorkSource(); ws1.add(50); @@ -193,6 +212,7 @@ public class WorkSourceTest extends TestCase { assertEquals(1, ws1.getWorkChains().get(0).getSize()); } + @Test public void testSet_nullWorkChain() { WorkSource ws = new WorkSource(); ws.add(60); @@ -203,6 +223,7 @@ public class WorkSourceTest extends TestCase { assertEquals(0, ws.getWorkChains().size()); } + @Test public void testAdd_workChains() { WorkSource ws = new WorkSource(); ws.createWorkChain().addNode(70, "foo"); @@ -224,6 +245,7 @@ public class WorkSourceTest extends TestCase { assertEquals(2, workChains.size()); } + @Test public void testSet_noWorkChains() { WorkSource ws = new WorkSource(); ws.set(10); @@ -237,6 +259,7 @@ public class WorkSourceTest extends TestCase { assertEquals("foo", ws2.getPackageName(0)); } + @Test public void testDiffChains_noChanges() { // WorkSources with no chains. assertEquals(null, WorkSource.diffChains(new WorkSource(), new WorkSource())); @@ -254,6 +277,7 @@ public class WorkSourceTest extends TestCase { assertEquals(null, WorkSource.diffChains(ws2, ws1)); } + @Test public void testDiffChains_noChains() { // Diffs against a worksource with no chains. WorkSource ws1 = new WorkSource(); @@ -276,6 +300,7 @@ public class WorkSourceTest extends TestCase { assertEquals(ws2.getWorkChains(), diffs[1]); } + @Test public void testDiffChains_onlyAdditionsOrRemovals() { WorkSource ws1 = new WorkSource(); WorkSource ws2 = new WorkSource(); @@ -302,6 +327,7 @@ public class WorkSourceTest extends TestCase { } + @Test public void testDiffChains_generalCase() { WorkSource ws1 = new WorkSource(); WorkSource ws2 = new WorkSource(); @@ -340,6 +366,7 @@ public class WorkSourceTest extends TestCase { assertEquals(new WorkChain().addNode(2, "tag2"), diffs[1].get(1)); } + @Test public void testGetAttributionId() { WorkSource ws = new WorkSource(); WorkChain wc1 = ws.createWorkChain(); @@ -355,6 +382,7 @@ public class WorkSourceTest extends TestCase { assertEquals(100, ws.getAttributionUid()); } + @Test public void testGetAttributionIdWithoutWorkChain() { WorkSource ws1 = new WorkSource(100); ws1.add(200); @@ -365,6 +393,7 @@ public class WorkSourceTest extends TestCase { assertEquals(100, ws2.getAttributionUid()); } + @Test public void testGetAttributionWhenEmpty() { WorkSource ws = new WorkSource(); assertEquals(-1, ws.getAttributionUid()); @@ -374,6 +403,7 @@ public class WorkSourceTest extends TestCase { assertNull(wc.getAttributionTag()); } + @Test public void testGetAttributionTag() { WorkSource ws1 = new WorkSource(); WorkChain wc = ws1.createWorkChain(); @@ -383,6 +413,7 @@ public class WorkSourceTest extends TestCase { assertEquals("tag", wc.getAttributionTag()); } + @Test public void testRemove_fromChainedWorkSource() { WorkSource ws1 = new WorkSource(); ws1.createWorkChain().addNode(50, "foo"); @@ -403,6 +434,7 @@ public class WorkSourceTest extends TestCase { assertEquals(75, ws1.getWorkChains().get(0).getAttributionUid()); } + @Test public void testRemove_fromSameWorkSource() { WorkSource ws1 = new WorkSource(50, "foo"); WorkSource ws2 = ws1; @@ -414,6 +446,7 @@ public class WorkSourceTest extends TestCase { assertEquals("foo", ws1.getPackageName(0)); } + @Test public void testTransferWorkChains() { WorkSource ws1 = new WorkSource(); WorkChain wc1 = ws1.createWorkChain().addNode(100, "tag"); diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index 7c4136d62b8f..9300d1e5cb95 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -831,6 +831,7 @@ public class DeviceConfigTest { } @Test + @FlakyTest(bugId = 299483542) public void onPropertiesChangedListener_setPropertiesCallback() { DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false); diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java index 612562eb22dc..e94273e1ada7 100644 --- a/core/tests/coretests/src/android/service/notification/ConditionTest.java +++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java @@ -21,9 +21,12 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; +import static org.junit.Assert.assertThrows; + import android.app.Flags; import android.net.Uri; import android.os.Parcel; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -36,6 +39,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; @RunWith(AndroidJUnit4.class) @SmallTest @@ -194,4 +198,59 @@ public class ConditionTest { Condition fromParcel = new Condition(parcel); assertThat(fromParcel).isEqualTo(cond); } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void constructor_unspecifiedSource_succeeds() { + new Condition(Uri.parse("id"), "Summary", Condition.STATE_TRUE); + // No exception. + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void constructor_validSource_succeeds() { + new Condition(Uri.parse("id"), "Summary", Condition.STATE_TRUE, Condition.SOURCE_CONTEXT); + // No exception. + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void constructor_invalidSource_throws() { + assertThrows(IllegalArgumentException.class, + () -> new Condition(Uri.parse("uri"), "Summary", Condition.STATE_TRUE, 1000)); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void constructor_parcelWithInvalidSource_throws() { + Condition original = new Condition(Uri.parse("condition"), "Summary", Condition.STATE_TRUE, + Condition.SOURCE_SCHEDULE); + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + + // Tweak the parcel to contain and invalid source value. + parcel.setDataPosition(parcel.dataPosition() - 8); // going back two int fields. + parcel.writeInt(100); + parcel.setDataPosition(0); + + assertThrows(IllegalArgumentException.class, () -> new Condition(parcel)); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void validate_invalidSource_throws() throws Exception { + Condition condition = new Condition(Uri.parse("condition"), "Summary", Condition.STATE_TRUE, + Condition.SOURCE_SCHEDULE); + + Field typeField = Condition.class.getDeclaredField("source"); + + // Reflection on reflection (ugh) to make a final field non-final + Field fieldAccessFlagsField = Field.class.getDeclaredField("accessFlags"); + fieldAccessFlagsField.setAccessible(true); + fieldAccessFlagsField.setInt(typeField, typeField.getModifiers() & ~Modifier.FINAL); + + typeField.setInt(condition, 30); + + assertThrows(IllegalArgumentException.class, condition::validate); + } } 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/com/android/internal/jank/CujTest.java b/core/tests/coretests/src/com/android/internal/jank/CujTest.java new file mode 100644 index 000000000000..bf35ed0a1601 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/jank/CujTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.jank; + +import static android.text.TextUtils.formatSimple; + +import static com.android.internal.jank.Cuj.getNameOfCuj; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.FrameworkStatsLog; + +import com.google.common.truth.Expect; + +import org.junit.Rule; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@SmallTest +public class CujTest { + private static final String ENUM_NAME_PREFIX = + "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__"; + private static final Set<String> DEPRECATED_VALUES = new HashSet<>() { + { + add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION"); + } + }; + private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = new HashMap<>() { + { + put(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD")); + put(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR")); + put(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH")); + put(Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR")); + put(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND")); + put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING")); + } + }; + + @Rule + public final Expect mExpect = Expect.create(); + + @Test + public void testCujNameLimit() { + getCujConstants().forEach(f -> { + final int cuj = getIntFieldChecked(f); + mExpect.withMessage(formatSimple("Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj))) + .that(getNameOfCuj(cuj).length()) + .isAtMost(Cuj.MAX_LENGTH_OF_CUJ_NAME); + }); + } + + @Test + public void testCujTypeEnumCorrectlyDefined() throws Exception { + List<Field> cujEnumFields = getCujConstants().toList(); + + HashSet<Integer> allValues = new HashSet<>(); + for (Field field : cujEnumFields) { + int fieldValue = field.getInt(null); + assertWithMessage("All CujType values must be unique. Field %s repeats existing value.", + field.getName()) + .that(allValues.add(fieldValue)) + .isTrue(); + assertWithMessage("Field %s must have a value <= LAST_CUJ", field.getName()) + .that(fieldValue) + .isAtMost(Cuj.LAST_CUJ); + assertWithMessage("Field %s must have a statsd mapping.", field.getName()) + .that(Cuj.logToStatsd(fieldValue)) + .isTrue(); + } + } + + @Test + public void testCujsMapToEnumsCorrectly() { + List<Field> cujs = getCujConstants().toList(); + + Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) + .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) + && !DEPRECATED_VALUES.contains(f.getName()) + && Modifier.isStatic(f.getModifiers()) + && f.getType() == int.class) + .collect(Collectors.toMap(CujTest::getIntFieldChecked, Field::getName)); + + assertThat(enumsMap.size() - 1).isEqualTo(cujs.size()); + + cujs.forEach(f -> { + final int cuj = getIntFieldChecked(f); + final String cujName = f.getName(); + final String expectedEnumName = + ENUM_NAME_EXCEPTION_MAP.getOrDefault(cuj, getEnumName(cujName.substring(4))); + final int enumKey = Cuj.getStatsdInteractionType(cuj); + final String enumName = enumsMap.get(enumKey); + final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj)); + + mExpect.withMessage( + formatSimple("%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey)) + .that(expectedEnumName.equals(enumName)) + .isTrue(); + mExpect.withMessage( + formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s", + cuj, cujName, expectedNameOfCuj)) + .that(cujName.equals(expectedNameOfCuj)) + .isTrue(); + }); + } + + private static String getEnumName(String name) { + return formatSimple("%s%s", ENUM_NAME_PREFIX, name); + } + + private static int getIntFieldChecked(Field field) { + try { + return field.getInt(null); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + + private static Stream<Field> getCujConstants() { + return Arrays.stream(Cuj.class.getDeclaredFields()) + .filter(f -> f.getName().startsWith("CUJ_") + && Modifier.isStatic(f.getModifiers()) + && f.getType() == int.class); + } +} diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java index 1b9717a6dfc5..1a7117e3b4a1 100644 --- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java @@ -22,9 +22,8 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_ import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import static com.android.internal.jank.FrameTracker.ViewRootWrapper; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_WALLPAPER_TRANSITION; +import static com.android.internal.jank.Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; +import static com.android.internal.jank.Cuj.CUJ_WALLPAPER_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED; import static com.google.common.truth.Truth.assertThat; @@ -49,15 +48,14 @@ import android.view.SurfaceControl.OnJankDataListener; import android.view.View; import android.view.ViewAttachTestActivity; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.filters.SmallTest; -import androidx.test.rule.ActivityTestRule; import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.StatsLogWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.InteractionJankMonitor.Configuration; -import com.android.internal.jank.InteractionJankMonitor.Session; import org.junit.Before; import org.junit.Rule; @@ -69,14 +67,14 @@ import java.util.concurrent.TimeUnit; @SmallTest public class FrameTrackerTest { - private static final String CUJ_POSTFIX = ""; + private static final String SESSION_NAME = "SessionName"; private static final long FRAME_TIME_60Hz = (long) 1e9 / 60; private ViewAttachTestActivity mActivity; @Rule - public ActivityTestRule<ViewAttachTestActivity> mRule = - new ActivityTestRule<>(ViewAttachTestActivity.class); + public ActivityScenarioRule<ViewAttachTestActivity> mRule = + new ActivityScenarioRule<>(ViewAttachTestActivity.class); private ThreadedRendererWrapper mRenderer; private FrameMetricsWrapper mWrapper; @@ -86,12 +84,13 @@ public class FrameTrackerTest { private StatsLogWrapper mStatsLog; private ArgumentCaptor<OnJankDataListener> mListenerCapture; private SurfaceControl mSurfaceControl; + private FrameTracker.FrameTrackerListener mTrackerListener; private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; @Before public void setup() { // Prepare an activity for getting ThreadedRenderer later. - mActivity = mRule.getActivity(); + mRule.getScenario().onActivity(activity -> mActivity = activity); View view = mActivity.getWindow().getDecorView(); assertThat(view.isAttachedToWindow()).isTrue(); @@ -116,36 +115,38 @@ public class FrameTrackerTest { mChoreographer = mock(ChoreographerWrapper.class); mStatsLog = mock(StatsLogWrapper.class); mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); + mTrackerListener = mock(FrameTracker.FrameTrackerListener.class); } - private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) { - InteractionJankMonitor monitor = mock(InteractionJankMonitor.class); - Handler handler = mRule.getActivity().getMainThreadHandler(); - Session session = new Session(cuj, postfix); + private FrameTracker spyFrameTracker(boolean surfaceOnly) { + Handler handler = mActivity.getMainThreadHandler(); Configuration config = mock(Configuration.class); + when(config.getSessionName()).thenReturn(SESSION_NAME); when(config.isSurfaceOnly()).thenReturn(surfaceOnly); when(config.getSurfaceControl()).thenReturn(mSurfaceControl); when(config.shouldDeferMonitor()).thenReturn(true); when(config.getDisplayId()).thenReturn(42); - View view = mRule.getActivity().getWindow().getDecorView(); + View view = mActivity.getWindow().getDecorView(); Handler spyHandler = spy(new Handler(handler.getLooper())); when(config.getView()).thenReturn(surfaceOnly ? null : view); when(config.getHandler()).thenReturn(spyHandler); + when(config.logToStatsd()).thenReturn(true); + when(config.getStatsdInteractionType()).thenReturn(surfaceOnly + ? Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION) + : Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)); FrameTracker frameTracker = Mockito.spy( - new FrameTracker(monitor, session, spyHandler, mRenderer, mViewRootWrapper, + new FrameTracker(config, mRenderer, mViewRootWrapper, mSurfaceControlWrapper, mChoreographer, mWrapper, mStatsLog, /* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1, - /* FrameTrackerListener= */ null, config)); - doNothing().when(frameTracker).triggerPerfetto(); + mTrackerListener)); doNothing().when(frameTracker).postTraceStartMarker(mRunnableArgumentCaptor.capture()); return frameTracker; } @Test public void testOnlyFirstWindowFrameOverThreshold() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); // Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP)) @@ -169,11 +170,11 @@ public class FrameTrackerTest { sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L); verify(tracker).removeObservers(); - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(5000000L) /* maxFrameTimeNanos */, @@ -184,8 +185,7 @@ public class FrameTrackerTest { @Test public void testSfJank() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -206,12 +206,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, eq(40000000L) /* maxFrameTimeNanos */, @@ -222,8 +222,7 @@ public class FrameTrackerTest { @Test public void testFirstFrameJankyNoTrigger() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -243,13 +242,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); - // We detected a janky frame - trigger Perfetto - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(4000000L) /* maxFrameTimeNanos */, @@ -260,8 +258,7 @@ public class FrameTrackerTest { @Test public void testOtherFrameOverThreshold() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -282,12 +279,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, eq(40000000L) /* maxFrameTimeNanos */, @@ -298,8 +295,7 @@ public class FrameTrackerTest { @Test public void testLastFrameOverThresholdBeforeEnd() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -323,12 +319,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, eq(50000000L) /* maxFrameTimeNanos */, @@ -342,8 +338,7 @@ public class FrameTrackerTest { */ @Test public void testNoOvercountingAfterEnd() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -367,11 +362,11 @@ public class FrameTrackerTest { sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L); verify(tracker).removeObservers(); - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(4000000L) /* maxFrameTimeNanos */, @@ -382,8 +377,7 @@ public class FrameTrackerTest { @Test public void testBeginCancel() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -402,13 +396,12 @@ public class FrameTrackerTest { tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); verify(tracker).removeObservers(); // Since the tracker has been cancelled, shouldn't trigger perfetto. - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); } @Test public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -426,13 +419,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // Should never trigger Perfetto since it is a cancel. - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); } @Test public void testCancelIfEndVsyncIdLessThanBeginVsyncId() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -450,13 +442,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // Should never trigger Perfetto since it is a cancel. - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); } @Test public void testCancelWhenSessionNeverBegun() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); verify(tracker).removeObservers(); @@ -464,8 +455,7 @@ public class FrameTrackerTest { @Test public void testEndWhenSessionNeverBegun() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); tracker.end(FrameTracker.REASON_END_NORMAL); verify(tracker).removeObservers(); @@ -473,8 +463,7 @@ public class FrameTrackerTest { @Test public void testSurfaceOnlyOtherFrameJanky() { - FrameTracker tracker = spyFrameTracker( - CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -495,12 +484,12 @@ public class FrameTrackerTest { sendFrame(tracker, JANK_NONE, 103L); verify(mSurfaceControlWrapper).removeJankStatsListener(any()); - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), + eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, eq(0L) /* maxFrameTimeNanos */, @@ -511,8 +500,7 @@ public class FrameTrackerTest { @Test public void testSurfaceOnlyFirstFrameJanky() { - FrameTracker tracker = spyFrameTracker( - CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -533,12 +521,12 @@ public class FrameTrackerTest { sendFrame(tracker, JANK_NONE, 103L); verify(mSurfaceControlWrapper).removeJankStatsListener(any()); - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), + eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(0L) /* maxFrameTimeNanos */, @@ -549,8 +537,7 @@ public class FrameTrackerTest { @Test public void testSurfaceOnlyLastFrameJanky() { - FrameTracker tracker = spyFrameTracker( - CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -571,12 +558,12 @@ public class FrameTrackerTest { sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L); verify(mSurfaceControlWrapper).removeJankStatsListener(any()); - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), + eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(0L) /* maxFrameTimeNanos */, @@ -587,8 +574,7 @@ public class FrameTrackerTest { @Test public void testMaxSuccessiveMissedFramesCount() { - FrameTracker tracker = spyFrameTracker( - CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); mRunnableArgumentCaptor.getValue().run(); @@ -604,11 +590,11 @@ public class FrameTrackerTest { sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L); sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L); verify(mSurfaceControlWrapper).removeJankStatsListener(any()); - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), + eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)), eq(6L) /* totalFrames */, eq(5L) /* missedFrames */, eq(0L) /* maxFrameTimeNanos */, diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index b61f995724e5..68095e5eb46c 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -20,23 +20,9 @@ import static android.text.TextUtils.formatSimple; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ADD; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE; -import static com.android.internal.jank.InteractionJankMonitor.MAX_LENGTH_OF_CUJ_NAME; -import static com.android.internal.jank.InteractionJankMonitor.getNameOfCuj; +import static com.android.internal.jank.InteractionJankMonitor.Configuration.generateSessionName; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -52,12 +38,11 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.provider.DeviceConfig; -import android.util.SparseArray; import android.view.View; import android.view.ViewAttachTestActivity; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.filters.SmallTest; -import androidx.test.rule.ActivityTestRule; import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; @@ -66,101 +51,54 @@ import com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.jank.InteractionJankMonitor.Configuration; -import com.android.internal.jank.InteractionJankMonitor.Session; -import com.android.internal.util.FrameworkStatsLog; import com.google.common.truth.Expect; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; @SmallTest public class InteractionJankMonitorTest { - private static final String CUJ_POSTFIX = ""; - private static final SparseArray<String> ENUM_NAME_EXCEPTION_MAP = new SparseArray<>(); - private static final String ENUM_NAME_PREFIX = - "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__"; - - private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>(); - private ViewAttachTestActivity mActivity; private View mView; + private Handler mHandler; private HandlerThread mWorker; @Rule - public ActivityTestRule<ViewAttachTestActivity> mRule = - new ActivityTestRule<>(ViewAttachTestActivity.class); + public ActivityScenarioRule<ViewAttachTestActivity> mRule = + new ActivityScenarioRule<>(ViewAttachTestActivity.class); @Rule public final Expect mExpect = Expect.create(); - @BeforeClass - public static void initialize() { - ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD")); - ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING")); - DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION"); - } - - private static String getEnumName(String name) { - return formatSimple("%s%s", ENUM_NAME_PREFIX, name); - } - @Before public void setup() { - // Prepare an activity for getting ThreadedRenderer later. - mActivity = mRule.getActivity(); + mRule.getScenario().onActivity(activity -> mActivity = activity); mView = mActivity.getWindow().getDecorView(); assertThat(mView.isAttachedToWindow()).isTrue(); - Handler handler = spy(new Handler(mActivity.getMainLooper())); - doReturn(true).when(handler).sendMessageAtTime(any(), anyLong()); + mHandler = spy(new Handler(mActivity.getMainLooper())); + doReturn(true).when(mHandler).sendMessageAtTime(any(), anyLong()); mWorker = mock(HandlerThread.class); - doReturn(handler).when(mWorker).getThreadHandler(); + doReturn(mHandler).when(mWorker).getThreadHandler(); } @Test public void testBeginEnd() { InteractionJankMonitor monitor = createMockedInteractionJankMonitor(); - FrameTracker tracker = createMockedFrameTracker(monitor, null); - doReturn(tracker).when(monitor).createFrameTracker(any(), any()); + FrameTracker tracker = createMockedFrameTracker(); + doReturn(tracker).when(monitor).createFrameTracker(any()); doNothing().when(tracker).begin(); doReturn(true).when(tracker).end(anyInt()); // Simulate a trace session and see if begin / end are invoked. - assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); + assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).begin(); - assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); + assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).end(REASON_END_NORMAL); } @@ -172,10 +110,10 @@ public class InteractionJankMonitorTest { propertiesValues.put("enabled", "false"); DeviceConfig.Properties properties = new DeviceConfig.Properties( DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues); - monitor.getPropertiesChangedListener().onPropertiesChanged(properties); + monitor.updateProperties(properties); - assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); - assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); } @Test @@ -185,145 +123,57 @@ public class InteractionJankMonitorTest { assertThat(view.isAttachedToWindow()).isFalse(); // Should return false if the view passed in is not attached to window yet. - assertThat(monitor.begin(view, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); - assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.begin(view, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); } @Test public void testBeginTimeout() { ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); InteractionJankMonitor monitor = createMockedInteractionJankMonitor(); - FrameTracker tracker = createMockedFrameTracker(monitor, null); - doReturn(tracker).when(monitor).createFrameTracker(any(), any()); + FrameTracker tracker = createMockedFrameTracker(); + doReturn(tracker).when(monitor).createFrameTracker(any()); doNothing().when(tracker).begin(); doReturn(true).when(tracker).cancel(anyInt()); - assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); + assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).begin(); - verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture()); + verify(monitor).scheduleTimeoutAction(any(), captor.capture()); Runnable runnable = captor.getValue(); assertThat(runnable).isNotNull(); - mWorker.getThreadHandler().removeCallbacks(runnable); runnable.run(); - verify(monitor).cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT); + verify(monitor).cancel(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT); verify(tracker).cancel(REASON_CANCEL_TIMEOUT); } @Test - public void testCujTypeEnumCorrectlyDefined() throws Exception { - List<Field> cujEnumFields = - Arrays.stream(InteractionJankMonitor.class.getDeclaredFields()) - .filter(field -> field.getName().startsWith("CUJ_") - && Modifier.isStatic(field.getModifiers()) - && field.getType() == int.class) - .collect(Collectors.toList()); - - HashSet<Integer> allValues = new HashSet<>(); - for (Field field : cujEnumFields) { - int fieldValue = field.getInt(null); - assertWithMessage( - "Field %s must have a mapping to a value in CUJ_TO_STATSD_INTERACTION_TYPE", - field.getName()) - .that(fieldValue < CUJ_TO_STATSD_INTERACTION_TYPE.length) - .isTrue(); - assertWithMessage("All CujType values must be unique. Field %s repeats existing value.", - field.getName()) - .that(allValues.add(fieldValue)) - .isTrue(); - } - } - - @Test - public void testCujsMapToEnumsCorrectly() { - List<Field> cujs = Arrays.stream(InteractionJankMonitor.class.getDeclaredFields()) - .filter(f -> f.getName().startsWith("CUJ_") - && Modifier.isStatic(f.getModifiers()) - && f.getType() == int.class) - .collect(Collectors.toList()); - - Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) - .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) - && !DEPRECATED_VALUES.contains(f.getName()) - && Modifier.isStatic(f.getModifiers()) - && f.getType() == int.class) - .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName)); - - assertThat(enumsMap.size() - 1).isEqualTo(cujs.size()); - - cujs.forEach(f -> { - final int cuj = getIntFieldChecked(f); - final String cujName = f.getName(); - final String expectedEnumName = ENUM_NAME_EXCEPTION_MAP.contains(cuj) - ? ENUM_NAME_EXCEPTION_MAP.get(cuj) - : formatSimple("%s%s", ENUM_NAME_PREFIX, cujName.substring(4)); - final int enumKey = CUJ_TO_STATSD_INTERACTION_TYPE[cuj]; - final String enumName = enumsMap.get(enumKey); - final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj)); - - mExpect - .withMessage(formatSimple( - "%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey)) - .that(expectedEnumName.equals(enumName)) - .isTrue(); - mExpect - .withMessage( - formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s", - cuj, cujName, expectedNameOfCuj)) - .that(cujName.equals(expectedNameOfCuj)) - .isTrue(); - }); - } - - @Test - public void testCujNameLimit() { - Arrays.stream(InteractionJankMonitor.class.getDeclaredFields()) - .filter(f -> f.getName().startsWith("CUJ_") - && Modifier.isStatic(f.getModifiers()) - && f.getType() == int.class) - .forEach(f -> { - final int cuj = getIntFieldChecked(f); - mExpect - .withMessage(formatSimple( - "Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj))) - .that(getNameOfCuj(cuj).length()) - .isAtMost(MAX_LENGTH_OF_CUJ_NAME); - }); - } - - @Test public void testSessionNameLengthLimit() { - final int cujType = CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; - final String cujName = getNameOfCuj(cujType); + final int cujType = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; + final String cujName = Cuj.getNameOfCuj(cujType); final String cujTag = "ThisIsTheCujTag"; final String tooLongTag = cujTag.repeat(10); // Normal case, no postfix. - Session noPostfix = new Session(cujType, ""); - assertThat(noPostfix.getName()).isEqualTo(formatSimple("J<%s>", cujName)); + assertThat(generateSessionName(cujName, "")).isEqualTo(formatSimple("J<%s>", cujName)); // Normal case, with postfix. - Session withPostfix = new Session(cujType, cujTag); - assertThat(withPostfix.getName()).isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag)); + assertThat(generateSessionName(cujName, cujTag)) + .isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag)); // Since the length of the cuj name is tested in another test, no need to test it here. // Too long postfix case, should trim the postfix and keep the cuj name completed. final String expectedTrimmedName = formatSimple("J<%s::%s>", cujName, "ThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThi..."); - Session longPostfix = new Session(cujType, tooLongTag); - assertThat(longPostfix.getName()).isEqualTo(expectedTrimmedName); + assertThat(generateSessionName(cujName, tooLongTag)).isEqualTo(expectedTrimmedName); } private InteractionJankMonitor createMockedInteractionJankMonitor() { InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); - doReturn(true).when(monitor).shouldMonitor(anyInt()); + doReturn(true).when(monitor).shouldMonitor(); return monitor; } - private FrameTracker createMockedFrameTracker(InteractionJankMonitor monitor, - FrameTracker.FrameTrackerListener listener) { - Session session = spy(new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX)); - doReturn(false).when(session).logToStatsd(); - + private FrameTracker createMockedFrameTracker() { ThreadedRendererWrapper threadedRenderer = mock(ThreadedRendererWrapper.class); doNothing().when(threadedRenderer).addObserver(any()); doNothing().when(threadedRenderer).removeObserver(any()); @@ -342,27 +192,19 @@ public class InteractionJankMonitorTest { Configuration configuration = mock(Configuration.class); when(configuration.isSurfaceOnly()).thenReturn(false); when(configuration.getView()).thenReturn(mView); - when(configuration.getHandler()).thenReturn(mView.getHandler()); when(configuration.getDisplayId()).thenReturn(42); + when(configuration.logToStatsd()).thenReturn(false); + when(configuration.getHandler()).thenReturn(mHandler); - FrameTracker tracker = spy(new FrameTracker(monitor, session, mWorker.getThreadHandler(), + FrameTracker tracker = spy(new FrameTracker(configuration, threadedRenderer, viewRoot, surfaceControl, choreographer, new FrameMetricsWrapper(), new StatsLogWrapper(null), /* traceThresholdMissedFrames= */ 1, - /* traceThresholdFrameTimeMillis= */ -1, listener, configuration)); + /* traceThresholdFrameTimeMillis= */ -1, + /* listener */ null)); doNothing().when(tracker).postTraceStartMarker(any()); - doNothing().when(tracker).triggerPerfetto(); - doReturn(configuration.getHandler()).when(tracker).getHandler(); return tracker; } - - private int getIntFieldChecked(Field field) { - try { - return field.getInt(null); - } catch (IllegalAccessException ex) { - throw new RuntimeException(ex); - } - } } diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java index 8207c9ee5ff3..b70f29038b31 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -29,7 +29,9 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.SystemClock; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArrayMap; import android.util.SparseArray; @@ -39,6 +41,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BinderInternal.CallSession; import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,13 +59,22 @@ import java.util.Set; @SmallTest @RunWith(AndroidJUnit4.class) @Presubmit +@IgnoreUnderRavenwood(blockedBy = BinderCallsStats.class) public class BinderCallsStatsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private static final int WORKSOURCE_UID = Process.FIRST_APPLICATION_UID; private static final int CALLING_UID = 2; private static final int REQUEST_SIZE = 2; private static final int REPLY_SIZE = 3; private final CachedDeviceState mDeviceState = new CachedDeviceState(false, true); - private final TestHandler mHandler = new TestHandler(); + private TestHandler mHandler; + + @Before + public void setUp() { + mHandler = new TestHandler(); + } @Test public void testDetailedOff() { diff --git a/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java b/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java index e4597b53b1a3..a1b80d24c48f 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java @@ -16,11 +16,19 @@ package com.android.internal.os; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import android.os.Binder; +import androidx.test.runner.AndroidJUnit4; + import com.android.internal.os.BinderCallHeavyHitterWatcher.HeavyHitterContainer; -import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; @@ -29,7 +37,8 @@ import java.util.Random; /** * Tests for {@link BinderCallHeavyHitterWatcher}. */ -public final class BinderHeavyHitterTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public final class BinderHeavyHitterTest { private boolean mListenerNotified = false; @@ -114,6 +123,7 @@ public final class BinderHeavyHitterTest extends TestCase { } } + @Test public void testPositive() throws Exception { BinderCallHeavyHitterWatcher watcher = BinderCallHeavyHitterWatcher.getInstance(); try { @@ -142,6 +152,7 @@ public final class BinderHeavyHitterTest extends TestCase { } } + @Test public void testNegative() throws Exception { BinderCallHeavyHitterWatcher watcher = BinderCallHeavyHitterWatcher.getInstance(); try { diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java index 7bd53b9d4fcc..31b55e69cf20 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java @@ -23,7 +23,9 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import android.os.Binder; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArrayMap; import android.util.proto.ProtoOutputStream; @@ -36,6 +38,7 @@ import com.android.internal.os.BinderLatencyProto.ApiStats; import com.android.internal.os.BinderLatencyProto.Dims; import com.android.internal.os.BinderLatencyProto.RepeatedApiStats; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,7 +50,11 @@ import java.util.Random; @SmallTest @RunWith(AndroidJUnit4.class) @Presubmit +@IgnoreUnderRavenwood(blockedBy = BinderLatencyObserver.class) public class BinderLatencyObserverTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Test public void testLatencyCollectionWithMultipleClasses() { TestBinderLatencyObserver blo = new TestBinderLatencyObserver(); diff --git a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java index e9f6450c13aa..5f02f046a647 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java @@ -20,11 +20,9 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import android.content.Context; import android.os.FileUtils; import android.util.IntArray; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -102,9 +100,8 @@ public class BinderfsStatsReaderTest { private boolean mHasError; @Before - public void setUp() { - Context context = InstrumentationRegistry.getContext(); - mStatsDirectory = context.getDir("binder_logs", Context.MODE_PRIVATE); + public void setUp() throws Exception { + mStatsDirectory = Files.createTempDirectory("BinderfsStatsReaderTest").toFile(); mFreezerBinderAsyncThreshold = 1024; mValidPids = IntArray.fromArray(new int[]{14505, 14461, 542, 540}, 4); mStatsPids = new IntArray(); diff --git a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java index 7f054d136639..2da3873341bb 100644 --- a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java @@ -16,11 +16,8 @@ package com.android.internal.os; -import static androidx.test.InstrumentationRegistry.getContext; - import static com.google.common.truth.Truth.assertThat; -import android.content.Context; import android.os.FileUtils; import androidx.test.runner.AndroidJUnit4; @@ -31,6 +28,7 @@ import org.junit.runner.RunWith; import java.io.File; import java.io.IOException; +import java.nio.file.Files; @RunWith(AndroidJUnit4.class) public class CpuScalingPolicyReaderTest { @@ -38,7 +36,7 @@ public class CpuScalingPolicyReaderTest { @Before public void setup() throws IOException { - File testDir = getContext().getDir("test", Context.MODE_PRIVATE); + File testDir = Files.createTempDirectory("CpuScalingPolicyReaderTest").toFile(); FileUtils.deleteContents(testDir); File policy0 = new File(testDir, "policy0"); diff --git a/core/tests/coretests/src/com/android/internal/os/DebugTest.java b/core/tests/coretests/src/com/android/internal/os/DebugTest.java index 2a8a8571c882..4371f2699d4f 100644 --- a/core/tests/coretests/src/com/android/internal/os/DebugTest.java +++ b/core/tests/coretests/src/com/android/internal/os/DebugTest.java @@ -16,14 +16,22 @@ package com.android.internal.os; +import static org.junit.Assert.assertTrue; + import android.os.Debug; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; -import junit.framework.TestCase; +import org.junit.Rule; +import org.junit.Test; @SmallTest -public class DebugTest extends TestCase { +@IgnoreUnderRavenwood(reason = "Requires ART support") +public class DebugTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private final static String EXPECTED_GET_CALLER = "com\\.android\\.internal\\.os\\.DebugTest\\.testGetCaller:\\d\\d"; @@ -39,6 +47,7 @@ public class DebugTest extends TestCase { return Debug.getCaller(); } + @Test public void testGetCaller() { assertTrue(callDepth0().matches(EXPECTED_GET_CALLER)); } @@ -62,6 +71,7 @@ public class DebugTest extends TestCase { return callDepth2(); } + @Test public void testGetCallers() { assertTrue(callDepth1().matches(EXPECTED_GET_CALLERS)); } @@ -69,6 +79,7 @@ public class DebugTest extends TestCase { /** * Regression test for b/31943543. Note: must be run under CheckJNI to detect the issue. */ + @Test public void testGetMemoryInfo() { Debug.MemoryInfo info = new Debug.MemoryInfo(); Debug.getMemoryInfo(-1, info); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java index cbd2ba4eeabc..1d8628d2ee55 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java @@ -24,6 +24,8 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.FileUtils; import android.os.SystemClock; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -31,6 +33,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -57,7 +60,11 @@ import java.util.stream.IntStream; */ @SmallTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuProcStringReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private File mRoot; private File mTestDir; private File mTestFile; diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java index b45f8d2733db..a57a40046a84 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java @@ -24,13 +24,16 @@ import static org.testng.Assert.assertThrows; import static java.util.stream.Collectors.toList; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -44,7 +47,10 @@ import java.util.Collections; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuThreadReaderDiffTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private MockitoSession mMockingSessions; @Mock KernelCpuThreadReader mMockReader; diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java index d43989c06a72..8c5e3d0e0724 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderEndToEndTest.java @@ -22,6 +22,8 @@ import static org.junit.Assert.assertTrue; import android.os.Process; import android.os.SystemClock; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; @@ -29,6 +31,7 @@ import androidx.test.filters.LargeTest; import com.android.internal.os.KernelCpuThreadReader.ProcessCpuUsage; import com.android.internal.os.KernelCpuThreadReader.ThreadCpuUsage; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,7 +50,10 @@ import java.util.stream.Collectors; */ @RunWith(AndroidJUnit4.class) @LargeTest +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuThreadReaderEndToEndTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private static final int TIMED_NUM_SAMPLES = 5; private static final int TIMED_START_MILLIS = 500; diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java index c3e4014bf29a..7eac2a31bee0 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java @@ -25,7 +25,9 @@ import static org.testng.Assert.assertThrows; import android.content.Context; import android.os.FileUtils; +import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -33,6 +35,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,7 +51,11 @@ import java.util.function.Predicate; @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuThreadReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private File mProcDirectory; @Before diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java index 2ccd74e99116..d35e0fc95aa1 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java @@ -21,6 +21,8 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.FileUtils; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; import android.util.SparseLongArray; @@ -31,6 +33,7 @@ import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeRead import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -51,7 +54,11 @@ import java.util.Random; */ @SmallTest @RunWith(Parameterized.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuUidActiveTimeReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private File mTestDir; private File mTestFile; private KernelCpuUidActiveTimeReader mReader; diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java index bda21c61375e..610e6aeb210d 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java @@ -23,11 +23,15 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.content.Context; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; import org.junit.runner.RunWith; import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator; @@ -50,7 +54,11 @@ import java.util.stream.IntStream; @SmallTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuUidBpfMapReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private Random mRand = new Random(12345); private KernelCpuUidTestBpfMapReader mReader; diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java index a0dab28a6da5..8807de089864 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.FileUtils; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; @@ -32,6 +34,7 @@ import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeRea import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -52,7 +55,11 @@ import java.util.Random; */ @SmallTest @RunWith(Parameterized.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuUidClusterTimeReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private File mTestDir; private File mTestFile; private KernelCpuUidClusterTimeReader mReader; diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java index 783f264d6772..b73034437350 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java @@ -24,6 +24,8 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.os.FileUtils; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; @@ -33,6 +35,7 @@ import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -55,7 +58,11 @@ import java.util.Random; */ @SmallTest @RunWith(Parameterized.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuUidFreqTimeReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private File mTestDir; private File mTestFile; private KernelCpuUidFreqTimeReader mReader; diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java index 1da1a9095d87..65072262c30f 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.os.FileUtils; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; @@ -33,6 +35,7 @@ import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeRea import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,7 +52,11 @@ import java.util.Random; */ @SmallTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelCpuUidUserSysTimeReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private File mTestDir; private File mTestFile; private KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader mReader; diff --git a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java index 60dac8520d14..ad5186e8e3c7 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java @@ -16,12 +16,18 @@ package com.android.internal.os; +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.LongSparseLongArray; import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; -import junit.framework.TestCase; - +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mockito; import java.io.BufferedReader; @@ -29,12 +35,17 @@ import java.io.BufferedReader; /** * Tests for KernelMemoryBandwidthStats parsing and delta calculation, based on memory_state_time. */ -public class KernelMemoryBandwidthStatsTest extends TestCase { +@RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") +public class KernelMemoryBandwidthStatsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); /** * Standard example of parsing stats. * @throws Exception */ + @Test @SmallTest public void testParseStandard() throws Exception { KernelMemoryBandwidthStats stats = new KernelMemoryBandwidthStats(); @@ -68,6 +79,7 @@ public class KernelMemoryBandwidthStatsTest extends TestCase { * zero. * @throws Exception */ + @Test @SmallTest public void testParseBackwards() throws Exception { KernelMemoryBandwidthStats zeroStats = new KernelMemoryBandwidthStats(); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java index 2de800bb6d00..f42d26de9847 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java @@ -19,9 +19,13 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; +import android.platform.test.annotations.IgnoreUnderRavenwood; +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; @@ -31,7 +35,10 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelSingleProcessCpuThreadReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); @Test public void getProcessCpuUsage() throws IOException { diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java index 74ab644f1376..120a4de54427 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; import androidx.test.filters.SmallTest; @@ -30,6 +32,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.os.KernelSingleUidTimeReader.Injector; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -43,7 +46,11 @@ import java.util.Collection; @SmallTest @RunWith(Parameterized.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class KernelSingleUidTimeReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private final static int TEST_UID = 2222; private final static int TEST_FREQ_COUNT = 5; diff --git a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java index cb8a62c0936c..632dce0fdc8b 100644 --- a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java @@ -16,9 +16,13 @@ package com.android.internal.os; -import androidx.test.filters.Suppress; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -import junit.framework.TestCase; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; import java.io.PrintWriter; import java.io.StringWriter; @@ -28,11 +32,12 @@ import java.util.Collections; import java.util.List; // this test causes a IllegalAccessError: superclass not accessible -@Suppress -public class LoggingPrintStreamTest extends TestCase { +@RunWith(AndroidJUnit4.class) +public class LoggingPrintStreamTest { TestPrintStream out = new TestPrintStream(); + @Test public void testPrintException() { @SuppressWarnings("ThrowableInstanceNeverThrown") Throwable t = new Throwable("Ignore me."); @@ -47,6 +52,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList(lines), out.lines); } + @Test public void testPrintObject() { Object o = new Object(); out.print(4); @@ -56,6 +62,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList("4" + o + "2"), out.lines); } + @Test public void testPrintlnObject() { Object o = new Object(); out.print(4); @@ -65,6 +72,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList("4" + o, "2"), out.lines); } + @Test public void testPrintf() { out.printf("Name: %s\nEmployer: %s", "Bob", "Google"); assertEquals(Arrays.asList("Name: Bob"), out.lines); @@ -72,6 +80,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList("Name: Bob", "Employer: Google"), out.lines); } + @Test public void testPrintInt() { out.print(4); out.print(2); @@ -80,12 +89,14 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Collections.singletonList("42"), out.lines); } + @Test public void testPrintlnInt() { out.println(4); out.println(2); assertEquals(Arrays.asList("4", "2"), out.lines); } + @Test public void testPrintCharArray() { out.print("Foo\nBar\nTee".toCharArray()); assertEquals(Arrays.asList("Foo", "Bar"), out.lines); @@ -93,6 +104,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines); } + @Test public void testPrintString() { out.print("Foo\nBar\nTee"); assertEquals(Arrays.asList("Foo", "Bar"), out.lines); @@ -100,22 +112,26 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines); } + @Test public void testPrintlnCharArray() { out.println("Foo\nBar\nTee".toCharArray()); assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines); } + @Test public void testPrintlnString() { out.println("Foo\nBar\nTee"); assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines); } + @Test public void testPrintlnStringWithBufferedData() { out.print(5); out.println("Foo\nBar\nTee"); assertEquals(Arrays.asList("5Foo", "Bar", "Tee"), out.lines); } + @Test public void testAppend() { out.append("Foo\n") .append('4') @@ -125,6 +141,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList("Foo", "4", "a"), out.lines); } + @Test public void testMultiByteCharactersSpanningBuffers() throws Exception { // assume 3*1000 bytes won't fit in LoggingPrintStream's internal buffer StringBuilder builder = new StringBuilder(); @@ -138,6 +155,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList(expected), out.lines); } + @Test public void testWriteOneByteAtATimeMultibyteCharacters() throws Exception { String expected = " \u20AC \u20AC \u20AC \u20AC "; for (byte b : expected.getBytes()) { @@ -147,6 +165,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList(expected), out.lines); } + @Test public void testWriteByteArrayAtATimeMultibyteCharacters() throws Exception { String expected = " \u20AC \u20AC \u20AC \u20AC "; out.write(expected.getBytes()); @@ -154,6 +173,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList(expected), out.lines); } + @Test public void testWriteWithOffsetsMultibyteCharacters() throws Exception { String expected = " \u20AC \u20AC \u20AC \u20AC "; byte[] bytes = expected.getBytes(); @@ -167,6 +187,7 @@ public class LoggingPrintStreamTest extends TestCase { assertEquals(Arrays.asList(expected), out.lines); } + @Test public void testWriteFlushesOnNewlines() throws Exception { String a = " \u20AC \u20AC "; String b = " \u20AC \u20AC "; 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 faccf1ad19a1..c9536b9b8129 100644 --- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java @@ -22,16 +22,22 @@ import static org.junit.Assert.assertThrows; import android.os.BadParcelableException; import android.os.Parcel; +import android.platform.test.annotations.IgnoreUnderRavenwood; +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; @RunWith(AndroidJUnit4.class) @SmallTest +@IgnoreUnderRavenwood(blockedBy = LongArrayMultiStateCounter.class) public class LongArrayMultiStateCounterTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); @Test public void setStateAndUpdateValue() { @@ -51,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/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java index 341375357902..e064e7483bfa 100644 --- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java @@ -22,16 +22,22 @@ import static org.junit.Assert.assertThrows; import android.os.BadParcelableException; import android.os.Parcel; +import android.platform.test.annotations.IgnoreUnderRavenwood; +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; @RunWith(AndroidJUnit4.class) @SmallTest +@IgnoreUnderRavenwood(blockedBy = LongMultiStateCounterTest.class) public class LongMultiStateCounterTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); @Test public void setStateAndUpdateValue() { diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java index 48baf09f5e10..dfb5cc35c5bc 100644 --- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java @@ -23,6 +23,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.platform.test.annotations.Presubmit; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -30,6 +31,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +42,9 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @Presubmit public final class LooperStatsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private HandlerThread mThreadFirst; private HandlerThread mThreadSecond; private Handler mHandlerFirst; diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java index 7951270461d7..0742052cce53 100644 --- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java @@ -43,14 +43,12 @@ public class MonotonicClockTest { assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); ByteArrayOutputStream out = new ByteArrayOutputStream(); - monotonicClock.writeXml(out, Xml.newFastSerializer()); - String xml = out.toString(); - assertThat(xml).contains("timeshift=\"1234\""); + monotonicClock.writeXml(out, Xml.newBinarySerializer()); mClock.realtime = 42; MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock); - newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()), - Xml.newFastPullParser()); + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + newMonotonicClock.readXml(in, Xml.newBinaryPullParser()); mClock.realtime = 2000; assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000); diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java index 77202d1faa31..c0f0714e52cc 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -27,6 +27,8 @@ import android.annotation.XmlRes; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -38,6 +40,7 @@ import com.android.internal.util.XmlUtils; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -50,7 +53,10 @@ import org.junit.runner.RunWith; */ @SmallTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = PowerProfile.class) public class PowerProfileTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); static final String TAG_TEST_MODEM = "test-modem"; static final String ATTR_NAME = "name"; diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java index 29da2319adc2..b99e2026ef26 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java @@ -21,17 +21,23 @@ import static com.google.common.truth.Truth.assertThat; import android.os.BatteryConsumer; import android.os.Parcel; import android.os.PersistableBundle; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class PowerStatsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private PowerStats.DescriptorRegistry mRegistry; private PowerStats.Descriptor mDescriptor; diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java index c3d40eb09237..f61fc7c33eb3 100644 --- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java @@ -18,11 +18,9 @@ package com.android.internal.os; import static org.junit.Assert.assertTrue; -import android.content.Context; import android.os.FileUtils; import android.util.IntArray; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -45,9 +43,8 @@ public class ProcLocksReaderTest implements private ArrayList<int[]> mPids = new ArrayList<>(); @Before - public void setUp() { - Context context = InstrumentationRegistry.getContext(); - mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE); + public void setUp() throws Exception { + mProcDirectory = Files.createTempDirectory("ProcLocksReaderTest").toFile(); } @After diff --git a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java index e97caf8c0631..3e4f34d71f5f 100644 --- a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java +++ b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java @@ -18,10 +18,8 @@ package com.android.internal.os; import static org.junit.Assert.assertEquals; -import android.content.Context; import android.os.FileUtils; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -41,9 +39,8 @@ public class ProcStatsUtilTest { private File mProcDirectory; @Before - public void setUp() { - Context context = InstrumentationRegistry.getContext(); - mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE); + public void setUp() throws Exception { + mProcDirectory = Files.createTempDirectory("ProcStatsUtilTest").toFile(); } @After diff --git a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java index 9db3f8a6e187..a706350c71a5 100644 --- a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java @@ -20,15 +20,16 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; -import android.content.Context; import android.os.FileUtils; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,14 +40,16 @@ import java.nio.file.Path; @SmallTest @RunWith(AndroidJUnit4.class) +@IgnoreUnderRavenwood(blockedBy = ProcTimeInStateReader.class) public class ProcTimeInStateReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private File mProcDirectory; @Before - public void setUp() { - Context context = InstrumentationRegistry.getContext(); - mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE); + public void setUp() throws Exception { + mProcDirectory = Files.createTempDirectory("ProcTimeInStateReaderTest").toFile(); } @After diff --git a/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java b/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java index 81cc9d830228..d11c500b055a 100644 --- a/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/os/ProcessCpuTrackerTest.java @@ -18,15 +18,23 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.SmallTest; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @SmallTest @RunWith(JUnit4.class) +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class ProcessCpuTrackerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Test public void testGetCpuTime() throws Exception { final ProcessCpuTracker tracker = new ProcessCpuTracker(false); diff --git a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java index 85eafc57acc3..84c93c232d9f 100644 --- a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java @@ -20,10 +20,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; -import android.content.Context; import android.os.FileUtils; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -48,7 +46,6 @@ import java.nio.file.Files; @RunWith(AndroidJUnit4.class) public class StoragedUidIoStatsReaderTest { - private File mRoot; private File mTestDir; private File mTestFile; // private Random mRand = new Random(); @@ -57,15 +54,10 @@ public class StoragedUidIoStatsReaderTest { @Mock private StoragedUidIoStatsReader.Callback mCallback; - private Context getContext() { - return InstrumentationRegistry.getContext(); - } - @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mTestDir = getContext().getDir("test", Context.MODE_PRIVATE); - mRoot = getContext().getFilesDir(); + mTestDir = Files.createTempDirectory("StoragedUidIoStatsReaderTest").toFile(); mTestFile = new File(mTestDir, "test.file"); mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(mTestFile.getAbsolutePath()); } @@ -73,10 +65,8 @@ public class StoragedUidIoStatsReaderTest { @After public void tearDown() throws Exception { FileUtils.deleteContents(mTestDir); - FileUtils.deleteContents(mRoot); } - /** * Tests that reading will never call the callback. */ diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java index 916e2b51ea53..6bdc06af2c5c 100644 --- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java +++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java @@ -63,8 +63,13 @@ public final class PhoneWindowTest { createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset); installDecor(); - assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode, - is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT)); + if (mPhoneWindow.mDefaultEdgeToEdge && !mPhoneWindow.isFloating()) { + assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode, + is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS)); + } else { + assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode, + is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT)); + } } @Test diff --git a/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java b/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java index e09cfd21e605..ae2ef0cb4e51 100644 --- a/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java @@ -34,10 +34,13 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.os.Parcel; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; import android.view.Display; import androidx.test.filters.SmallTest; +import org.junit.Rule; import org.junit.Test; import java.util.Arrays; @@ -46,7 +49,10 @@ import java.util.Arrays; * Test class for {@link EnergyConsumerStats}. */ @SmallTest +@IgnoreUnderRavenwood(reason = "Needs kernel support") public class EnergyConsumerStatsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); @Test public void testConstruction() { 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/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 2237ba1924db..aaddf0e50ddd 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -169,6 +169,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/TaskOrganizerController.java" }, + "-1961637874": { + "message": "DeferredDisplayUpdater: applying DisplayInfo immediately", + "level": "DEBUG", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" + }, "-1949279037": { "message": "Attempted to add input method window with bad token %s. Aborting.", "level": "WARN", @@ -313,6 +319,12 @@ "group": "WM_DEBUG_RESIZE", "at": "com\/android\/server\/wm\/WindowState.java" }, + "-1818910559": { + "message": "DeferredDisplayUpdater: applied DisplayInfo after deferring", + "level": "DEBUG", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" + }, "-1814361639": { "message": "Set IME snapshot position: (%d, %d)", "level": "INFO", @@ -595,6 +607,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1518132958": { + "message": "fractionRendered boundsOverSource=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-1517908912": { "message": "requestScrollCapture: caught exception dispatching to window.token=%s", "level": "WARN", @@ -961,6 +979,12 @@ "group": "WM_DEBUG_CONTENT_RECORDING", "at": "com\/android\/server\/wm\/ContentRecorder.java" }, + "-1209762265": { + "message": "Registering listener=%s with id=%d for window=%s with %s", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-1209252064": { "message": "Clear animatingExit: reason=clearAnimatingFlags win=%s", "level": "DEBUG", @@ -1333,6 +1357,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-888703350": { + "message": "Skipping %s", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-883738232": { "message": "Adding more than one toast window for UID at a time.", "level": "WARN", @@ -1909,6 +1939,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "-415346336": { + "message": "DeferredDisplayUpdater: partially applying DisplayInfo immediately", + "level": "DEBUG", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" + }, "-401282500": { "message": "destroyIfPossible: r=%s destroy returned removed=%s", "level": "DEBUG", @@ -1957,6 +1993,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "-376950429": { + "message": "DeferredDisplayUpdater: deferring DisplayInfo update", + "level": "DEBUG", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" + }, "-374767836": { "message": "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s", "level": "VERBOSE", @@ -2803,6 +2845,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "360319850": { + "message": "fractionRendered scale=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "364992694": { "message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s", "level": "VERBOSE", @@ -2983,6 +3031,12 @@ "group": "WM_DEBUG_BACK_PREVIEW", "at": "com\/android\/server\/wm\/BackNavigationController.java" }, + "532771960": { + "message": "Adding untrusted state listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "535103992": { "message": "Wallpaper may change! Adjusting", "level": "VERBOSE", @@ -3061,6 +3115,12 @@ "group": "WM_DEBUG_DREAM", "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" }, + "605179032": { + "message": "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "608694300": { "message": " NEW SURFACE SESSION %s", "level": "INFO", @@ -3289,6 +3349,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowState.java" }, + "824532141": { + "message": "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f minFractionRendered=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "829434921": { "message": "Draw state now committed in %s", "level": "VERBOSE", @@ -3583,6 +3649,12 @@ "group": "WM_SHOW_SURFACE_ALLOC", "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java" }, + "1090378847": { + "message": "Checking %d windows", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1100065297": { "message": "Attempted to get IME policy of a display that does not exist: %d", "level": "WARN", @@ -3715,6 +3787,12 @@ "group": "WM_DEBUG_FOCUS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1251721200": { + "message": "unregister failed, couldn't find deathRecipient for %s with id=%d", + "level": "ERROR", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1252594551": { "message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d", "level": "WARN", @@ -3853,6 +3931,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/TaskDisplayArea.java" }, + "1382634842": { + "message": "Unregistering listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1393721079": { "message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]", "level": "VERBOSE", @@ -3901,6 +3985,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "1445704347": { + "message": "coveredRegionsAbove updated with %s frame:%s region:%s", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1448683958": { "message": "Override pending remote transitionSet=%b adapter=%s", "level": "INFO", @@ -4201,6 +4291,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "1786463281": { + "message": "Adding trusted state listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1789321832": { "message": "Then token:%s is invalid. It might be removed", "level": "WARN", @@ -4375,6 +4471,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "1955470028": { + "message": "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s scale=%f,%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1964565370": { "message": "Starting remote animation", "level": "INFO", @@ -4659,6 +4761,9 @@ "WM_DEBUG_TASKS": { "tag": "WindowManager" }, + "WM_DEBUG_TPL": { + "tag": "WindowManager" + }, "WM_DEBUG_WALLPAPER": { "tag": "WindowManager" }, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 15d14e87fcf6..b315f94b5d00 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -241,7 +241,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, for (int i = 0; i < displays.length; i++) { DisplayAddress.Physical address = (DisplayAddress.Physical) displays[i].getAddress(); - if (mRearDisplayAddress == address.getPhysicalDisplayId()) { + if (address != null && mRearDisplayAddress == address.getPhysicalDisplayId()) { rearDisplayMetrics = new DisplayMetrics(); final Display rearDisplay = displays[i]; 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/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 58436351885a..cf858dcb0637 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -17,6 +17,7 @@ package com.android.wm.shell.back; import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME; +import static com.android.window.flags.Flags.predictiveBackSystemAnimations; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; @@ -221,6 +222,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void onInit() { setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); + updateEnableAnimationFromFlags(); createAdapter(); mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, this::createExternalInterface, this); @@ -229,28 +231,39 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void setupAnimationDeveloperSettingsObserver( @NonNull ContentResolver contentResolver, @NonNull @ShellBackgroundThread final Handler backgroundHandler) { + if (predictiveBackSystemAnimations()) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore " + + "developer settings flag is ignored and no content observer registered"); + return; + } ContentObserver settingsObserver = new ContentObserver(backgroundHandler) { @Override public void onChange(boolean selfChange, Uri uri) { - updateEnableAnimationFromSetting(); + updateEnableAnimationFromFlags(); } }; contentResolver.registerContentObserver( Global.getUriFor(Global.ENABLE_BACK_ANIMATION), false, settingsObserver, UserHandle.USER_SYSTEM ); - updateEnableAnimationFromSetting(); } + /** + * Updates {@link BackAnimationController#mEnableAnimations} based on the current values of the + * aconfig flag and the developer settings flag + */ @ShellBackgroundThread - private void updateEnableAnimationFromSetting() { - int settingValue = Global.getInt(mContext.getContentResolver(), - Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF); - boolean isEnabled = settingValue == SETTING_VALUE_ON; + private void updateEnableAnimationFromFlags() { + boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled(); mEnableAnimations.set(isEnabled); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); } + private boolean isDeveloperSettingEnabled() { + return Global.getInt(mContext.getContentResolver(), + Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF) == SETTING_VALUE_ON; + } + public BackAnimation getBackAnimationImpl() { return mBackAnimation; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java index dc413b059fd7..a32b435ff99e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java @@ -28,7 +28,7 @@ import android.view.RemoteAnimationTarget; import android.window.IBackAnimationRunner; import android.window.IOnBackInvokedCallback; -import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.jank.Cuj.CujType; import com.android.wm.shell.common.InteractionJankMonitorUtils; /** @@ -42,7 +42,7 @@ public class BackAnimationRunner { private final IOnBackInvokedCallback mCallback; private final IRemoteAnimationRunner mRunner; - private final @InteractionJankMonitor.CujType int mCujType; + private final @CujType int mCujType; private final Context mContext; // Whether we are waiting to receive onAnimationStart @@ -55,7 +55,7 @@ public class BackAnimationRunner { @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner, @NonNull Context context, - @InteractionJankMonitor.CujType int cujType) { + @CujType int cujType) { mCallback = callback; mRunner = runner; mCujType = cujType; 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/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java index ec344d345139..86f00b83cadd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java @@ -23,6 +23,7 @@ import android.text.TextUtils; import android.view.SurfaceControl; import android.view.View; +import com.android.internal.jank.Cuj.CujType; import com.android.internal.jank.InteractionJankMonitor; /** Utils class for simplfy InteractionJank trancing call */ @@ -31,11 +32,11 @@ public class InteractionJankMonitorUtils { /** * Begin a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link CujType}. * @param view the view to trace * @param tag the tag to distinguish different flow of same type CUJ. */ - public static void beginTracing(@InteractionJankMonitor.CujType int cujType, + public static void beginTracing(@CujType int cujType, @NonNull View view, @Nullable String tag) { final InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withView(cujType, view); @@ -48,12 +49,12 @@ public class InteractionJankMonitorUtils { /** * Begin a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link CujType}. * @param context the context * @param surface the surface to trace * @param tag the tag to distinguish different flow of same type CUJ. */ - public static void beginTracing(@InteractionJankMonitor.CujType int cujType, + public static void beginTracing(@CujType int cujType, @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) { final InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface); @@ -66,18 +67,18 @@ public class InteractionJankMonitorUtils { /** * End a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link CujType}. */ - public static void endTracing(@InteractionJankMonitor.CujType int cujType) { + public static void endTracing(@CujType int cujType) { InteractionJankMonitor.getInstance().end(cujType); } /** * Cancel the trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link CujType}. */ - public static void cancelTracing(@InteractionJankMonitor.CujType int cujType) { + public static void cancelTracing(@CujType int cujType) { InteractionJankMonitor.getInstance().cancel(cujType); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 0f0fbd9cc12f..f801b0d01084 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -83,6 +83,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { private int mStartPos; private GestureDetector mDoubleTapDetector; private boolean mInteractive; + private boolean mHideHandle; private boolean mSetTouchRegion = true; private int mLastDraggingPosition; private int mHandleRegionWidth; @@ -211,11 +212,8 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { } /** Sets up essential dependencies of the divider bar. */ - public void setup( - SplitLayout layout, - SplitWindowManager splitWindowManager, - SurfaceControlViewHost viewHost, - InsetsState insetsState) { + public void setup(SplitLayout layout, SplitWindowManager splitWindowManager, + SurfaceControlViewHost viewHost, InsetsState insetsState) { mSplitLayout = layout; mSplitWindowManager = splitWindowManager; mViewHost = viewHost; @@ -277,6 +275,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { R.dimen.docked_stack_divider_lift_elevation); mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener()); mInteractive = true; + mHideHandle = false; setOnTouchListener(this); mHandle.setAccessibilityDelegate(mHandleDelegate); setWillNotDraw(false); @@ -469,10 +468,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { void setInteractive(boolean interactive, boolean hideHandle, String from) { if (interactive == mInteractive) return; ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, - "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive", - from); + "Set divider bar %s hide handle=%b from %s", + interactive ? "interactive" : "non-interactive", hideHandle, from); mInteractive = interactive; - if (!mInteractive && hideHandle && mMoving) { + mHideHandle = hideHandle; + if (!mInteractive && mHideHandle && mMoving) { final int position = mSplitLayout.getDividePosition(); mSplitLayout.flingDividePosition( mLastDraggingPosition, @@ -482,7 +482,15 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { mMoving = false; } releaseTouching(); - mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE); + mHandle.setVisibility(!mInteractive && mHideHandle ? View.INVISIBLE : View.VISIBLE); + } + + boolean isInteractive() { + return mInteractive; + } + + boolean isHandleHidden() { + return mHideHandle; } private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index b699533374df..53caddb52f23 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -59,6 +59,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; @@ -70,6 +71,7 @@ import com.android.wm.shell.common.InteractionJankMonitorUtils; import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; import java.util.function.Consumer; @@ -420,7 +422,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void init() { if (mInitialized) return; mInitialized = true; - mSplitWindowManager.init(this, mInsetsState); + mSplitWindowManager.init(this, mInsetsState, false /* isRestoring */); mDisplayImeController.addPositionProcessor(mImePositionProcessor); } @@ -442,14 +444,19 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } /** Releases and re-inflates {@link DividerView} on the root surface. */ - public void update(SurfaceControl.Transaction t) { + public void update(SurfaceControl.Transaction t, boolean resetImePosition) { if (!mInitialized) { init(); return; } mSplitWindowManager.release(t); - mImePositionProcessor.reset(); - mSplitWindowManager.init(this, mInsetsState); + if (resetImePosition) { + mImePositionProcessor.reset(); + } + mSplitWindowManager.init(this, mInsetsState, true /* isRestoring */); + // Update the surface positions again after recreating the divider in case nothing else + // triggers it + mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); } @Override @@ -868,6 +875,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange pw.println(prefix + TAG + ":"); pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait); pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit); + pw.println(innerPrefix + "mFreezeDividerWindow=" + mFreezeDividerWindow); + pw.println(innerPrefix + "mDimNonImeSide=" + mDimNonImeSide); + pw.println(innerPrefix + "mDividerPosition=" + mDividerPosition); pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString()); pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString()); pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString()); @@ -1151,14 +1161,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mTargetYOffset = needOffset ? getTargetYOffset() : 0; if (mTargetYOffset != mLastYOffset) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Split IME animation starting, fromY=%d toY=%d", + mLastYOffset, mTargetYOffset); // Freeze the configuration size with offset to prevent app get a configuration // changed or relaunch. This is required to make sure client apps will calculate // insets properly after layout shifted. if (mTargetYOffset == 0) { mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this); } else { - mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset, - SplitLayout.this); + mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this); } } @@ -1183,6 +1195,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void onImeEndPositioning(int displayId, boolean cancel, SurfaceControl.Transaction t) { if (displayId != mDisplayId || !mHasImeFocus || cancel) return; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Split IME animation ending, canceled=%b", cancel); onProgress(1.0f); mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java index 00361d9dd9cf..8fb9bda539a0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java @@ -62,6 +62,10 @@ public final class SplitWindowManager extends WindowlessWindowManager { // Used to "pass" a transaction to WWM.remove so that view removal can be synchronized. private SurfaceControl.Transaction mSyncTransaction = null; + // For saving/restoring state + private boolean mLastDividerInteractive = true; + private boolean mLastDividerHandleHidden; + public interface ParentContainerCallbacks { void attachToParentSurface(SurfaceControl.Builder b); void onLeashReady(SurfaceControl leash); @@ -107,7 +111,7 @@ public final class SplitWindowManager extends WindowlessWindowManager { } /** Inflates {@link DividerView} on to the root surface. */ - void init(SplitLayout splitLayout, InsetsState insetsState) { + void init(SplitLayout splitLayout, InsetsState insetsState, boolean isRestoring) { if (mDividerView != null || mViewHost != null) { throw new UnsupportedOperationException( "Try to inflate divider view again without release first"); @@ -130,6 +134,10 @@ public final class SplitWindowManager extends WindowlessWindowManager { lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider); mViewHost.setView(mDividerView, lp); mDividerView.setup(splitLayout, this, mViewHost, insetsState); + if (isRestoring) { + mDividerView.setInteractive(mLastDividerInteractive, mLastDividerHandleHidden, + "restore_setup"); + } } /** @@ -138,6 +146,8 @@ public final class SplitWindowManager extends WindowlessWindowManager { */ void release(@Nullable SurfaceControl.Transaction t) { if (mDividerView != null) { + mLastDividerInteractive = mDividerView.isInteractive(); + mLastDividerHandleHidden = mDividerView.isHandleHidden(); mDividerView = null; } 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/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index 38ce16489b06..d157ca837608 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -16,8 +16,8 @@ package com.android.wm.shell.onehanded; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_ENTER_TRANSITION; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_EXIT_TRANSITION; +import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_ENTER_TRANSITION; +import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_EXIT_TRANSITION; import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT; import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER; @@ -37,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.internal.jank.Cuj.CujType; import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; @@ -327,7 +328,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mTransitionCallbacks.add(callback); } - void beginCUJTracing(@InteractionJankMonitor.CujType int cujType, @Nullable String tag) { + void beginCUJTracing(@CujType int cujType, @Nullable String tag) { final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry = getDisplayAreaTokenMap().entrySet().iterator().next(); final InteractionJankMonitor.Configuration.Builder builder = @@ -339,11 +340,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mJankMonitor.begin(builder); } - void endCUJTracing(@InteractionJankMonitor.CujType int cujType) { + void endCUJTracing(@CujType int cujType) { mJankMonitor.end(cujType); } - void cancelCUJTracing(@InteractionJankMonitor.CujType int cujType) { + void cancelCUJTracing(@CujType int cujType) { mJankMonitor.cancel(cujType); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 8eb4a5a1a4c9..4c477373c32c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -769,7 +769,7 @@ public class PipAnimationController { getSurfaceTransactionHelper().crop(tx, leash, destBounds); } if (mContentOverlay != null) { - mContentOverlay.onAnimationEnd(tx, destBounds); + clearContentOverlay(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index 850b06a0fb7d..e11e8596a7fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -67,15 +67,6 @@ public abstract class PipContentOverlay { public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction); - /** - * Callback when reaches the end of animation on the internal {@link #mLeash}. - * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly - * call apply on this transaction, it should be applied on the caller side. - * @param destinationBounds {@link Rect} of the final bounds. - */ - public abstract void onAnimationEnd(SurfaceControl.Transaction atomicTx, - Rect destinationBounds); - /** A {@link PipContentOverlay} uses solid color. */ public static final class PipColorOverlay extends PipContentOverlay { private static final String TAG = PipColorOverlay.class.getSimpleName(); @@ -107,11 +98,6 @@ public abstract class PipContentOverlay { atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); } - @Override - public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - // Do nothing. Color overlay should be fully opaque by now. - } - private float[] getContentOverlayColor(Context context) { final TypedArray ta = context.obtainStyledAttributes(new int[] { android.R.attr.colorBackground }); @@ -164,11 +150,6 @@ public abstract class PipContentOverlay { Rect currentBounds, float fraction) { // Do nothing. Keep the snapshot till animation ends. } - - @Override - public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - atomicTx.remove(mLeash); - } } /** A {@link PipContentOverlay} shows app icon on solid color background. */ @@ -193,19 +174,12 @@ public abstract class PipContentOverlay { MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics()); mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx); - final int appWidth = appBounds.width(); - final int appHeight = appBounds.height(); - - // In order to have the overlay always cover the pip window during the transition, the - // overlay will be drawn with the max size of the start and end bounds in different - // rotation. - final int overlaySize = Math.max(Math.max(appWidth, appHeight), - Math.max(destinationBounds.width(), destinationBounds.height())) + 1; + final int overlaySize = getOverlaySize(appBounds, destinationBounds); mOverlayHalfSize = overlaySize >> 1; // When the activity is in the secondary split, make sure the scaling center is not // offset. - mAppBounds = new Rect(0, 0, appWidth, appHeight); + mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height()); mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888); prepareAppIconOverlay(appIcon); @@ -215,6 +189,21 @@ public abstract class PipContentOverlay { .build(); } + /** + * Returns the size of the app icon overlay. + * + * In order to have the overlay always cover the pip window during the transition, + * the overlay will be drawn with the max size of the start and end bounds in different + * rotation. + */ + public static int getOverlaySize(Rect appBounds, Rect destinationBounds) { + final int appWidth = appBounds.width(); + final int appHeight = appBounds.height(); + + return Math.max(Math.max(appWidth, appHeight), + Math.max(destinationBounds.width(), destinationBounds.height())) + 1; + } + @Override public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { tx.show(mLeash); @@ -247,11 +236,6 @@ public abstract class PipContentOverlay { } @Override - public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - atomicTx.remove(mLeash); - } - - @Override public void detach(SurfaceControl.Transaction tx) { super.detach(tx); if (mBitmap != null && !mBitmap.isRecycled()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index c1164fca22f2..3635165d76ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -156,74 +156,77 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // These callbacks are called on the update thread private final PipAnimationController.PipAnimationCallback mPipAnimationCallback = new PipAnimationController.PipAnimationCallback() { - private boolean mIsCancelled; - @Override - public void onPipAnimationStart(TaskInfo taskInfo, - PipAnimationController.PipTransitionAnimator animator) { - final int direction = animator.getTransitionDirection(); - mIsCancelled = false; - sendOnPipTransitionStarted(direction); - } - - @Override - public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, - PipAnimationController.PipTransitionAnimator animator) { - final int direction = animator.getTransitionDirection(); - if (mIsCancelled) { - sendOnPipTransitionFinished(direction); - maybePerformFinishResizeCallback(); - return; - } - final int animationType = animator.getAnimationType(); - final Rect destinationBounds = animator.getDestinationBounds(); - if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) { - fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay, true /* withStartDelay*/); - } - if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS - && direction == TRANSITION_DIRECTION_TO_PIP) { - // Notify the display to continue the deferred orientation change. - final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.scheduleFinishEnterPip(mToken, destinationBounds); - mTaskOrganizer.applyTransaction(wct); - // The final task bounds will be applied by onFixedRotationFinished so that all - // coordinates are in new rotation. - mSurfaceTransactionHelper.round(tx, mLeash, isInPip()); - mDeferredAnimEndTransaction = tx; - return; - } - final boolean isExitPipDirection = isOutPipDirection(direction) - || isRemovePipDirection(direction); - if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP - || isExitPipDirection) { - // execute the finish resize callback if needed after the transaction is committed - tx.addTransactionCommittedListener(mMainExecutor, - PipTaskOrganizer.this::maybePerformFinishResizeCallback); - - // Finish resize as long as we're not exiting PIP, or, if we are, only if this is - // the end of an exit PIP animation. - // This is necessary in case there was a resize animation ongoing when exit PIP - // started, in which case the first resize will be skipped to let the exit - // operation handle the final resize out of PIP mode. See b/185306679. - finishResizeDelayedIfNeeded(() -> { - finishResize(tx, destinationBounds, direction, animationType); - sendOnPipTransitionFinished(direction); - }); - } - } + private boolean mIsCancelled; - @Override - public void onPipAnimationCancel(TaskInfo taskInfo, - PipAnimationController.PipTransitionAnimator animator) { - final int direction = animator.getTransitionDirection(); - mIsCancelled = true; - if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) { - fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay, true /* withStartDelay */); - } - sendOnPipTransitionCancelled(direction); - } - }; + @Override + public void onPipAnimationStart(TaskInfo taskInfo, + PipAnimationController.PipTransitionAnimator animator) { + final int direction = animator.getTransitionDirection(); + mIsCancelled = false; + sendOnPipTransitionStarted(direction); + } + + @Override + public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, + PipAnimationController.PipTransitionAnimator animator) { + final int direction = animator.getTransitionDirection(); + if (mIsCancelled) { + sendOnPipTransitionFinished(direction); + maybePerformFinishResizeCallback(); + return; + } + final int animationType = animator.getAnimationType(); + final Rect destinationBounds = animator.getDestinationBounds(); + if (isInPipDirection(direction) && mPipOverlay != null) { + fadeOutAndRemoveOverlay(mPipOverlay, + null /* callback */, true /* withStartDelay*/); + } + if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS + && direction == TRANSITION_DIRECTION_TO_PIP) { + // Notify the display to continue the deferred orientation change. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.scheduleFinishEnterPip(mToken, destinationBounds); + mTaskOrganizer.applyTransaction(wct); + // The final task bounds will be applied by onFixedRotationFinished so + // that all coordinates are in new rotation. + mSurfaceTransactionHelper.round(tx, mLeash, isInPip()); + mDeferredAnimEndTransaction = tx; + return; + } + final boolean isExitPipDirection = isOutPipDirection(direction) + || isRemovePipDirection(direction); + if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP + || isExitPipDirection) { + // execute the finish resize callback if needed after the transaction is + // committed + tx.addTransactionCommittedListener(mMainExecutor, + PipTaskOrganizer.this::maybePerformFinishResizeCallback); + + // Finish resize as long as we're not exiting PIP, or, if we are, only if + // this is the end of an exit PIP animation. + // This is necessary in case there was a resize animation ongoing when + // exit PIP started, in which case the first resize will be skipped to + // let the exit operation handle the final resize out of PIP mode. + // See b/185306679. + finishResizeDelayedIfNeeded(() -> { + finishResize(tx, destinationBounds, direction, animationType); + sendOnPipTransitionFinished(direction); + }); + } + } + + @Override + public void onPipAnimationCancel(TaskInfo taskInfo, + PipAnimationController.PipTransitionAnimator animator) { + final int direction = animator.getTransitionDirection(); + mIsCancelled = true; + if (isInPipDirection(direction) && mPipOverlay != null) { + fadeOutAndRemoveOverlay(mPipOverlay, + null /* callback */, true /* withStartDelay */); + } + sendOnPipTransitionCancelled(direction); + } + }; /** * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu. @@ -326,11 +329,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private @Surface.Rotation int mCurrentRotation; /** - * An optional overlay used to mask content changing between an app in/out of PiP, only set if - * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true. + * An optional overlay used to mask content changing between an app in/out of PiP. */ @Nullable - SurfaceControl mSwipePipToHomeOverlay; + SurfaceControl mPipOverlay; public PipTaskOrganizer(Context context, @NonNull SyncTransactionQueue syncTransactionQueue, @@ -470,7 +472,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } mPipBoundsState.setBounds(destinationBounds); - mSwipePipToHomeOverlay = overlay; + mPipOverlay = overlay; if (ENABLE_SHELL_TRANSITIONS && overlay != null) { // With Shell transition, the overlay was attached to the remote transition leash, which // will be removed when the current transition is finished, so we need to reparent it @@ -882,7 +884,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } final Rect destinationBounds = mPipBoundsState.getBounds(); - final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay; + final SurfaceControl swipeToHomeOverlay = mPipOverlay; final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper .resetScale(tx, mLeash, destinationBounds) @@ -901,7 +903,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }, tx); mPipTransitionState.setInSwipePipToHomeTransition(false); - mSwipePipToHomeOverlay = null; + mPipOverlay = null; } private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable, @@ -1116,9 +1118,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } clearWaitForFixedRotation(); - if (mSwipePipToHomeOverlay != null) { - removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */); - mSwipePipToHomeOverlay = null; + if (mPipOverlay != null) { + removeContentOverlay(mPipOverlay, null /* callback */); + mPipOverlay = null; } resetShadowRadius(); mPipTransitionState.setInSwipePipToHomeTransition(false); @@ -1766,6 +1768,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, animator.setSnapshotContentOverlay(snapshot, sourceHintRect); } } + mPipOverlay = animator.getContentOverlayLeash(); // The destination bounds are used for the end rect of animation and the final bounds // after animation finishes. So after the animation is started, the destination bounds // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout @@ -1879,6 +1882,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } private void removeContentOverlay(SurfaceControl surface, Runnable callback) { + if (mPipOverlay != null) { + if (mPipOverlay != surface) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: trying to remove overlay (%s) which is not local reference (%s)", + TAG, surface, mPipOverlay); + } + mPipOverlay = null; + } if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) { // Avoid double removal, which is fatal. return; @@ -1907,11 +1918,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private void cancelCurrentAnimator() { final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController.getCurrentAnimator(); + // remove any overlays if present + if (mPipOverlay != null) { + removeContentOverlay(mPipOverlay, null /* callback */); + } if (animator != null) { - if (animator.getContentOverlayLeash() != null) { - removeContentOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay); - } PipAnimationController.quietCancel(animator); mPipAnimationController.resetAnimatorState(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index fe4980a9eb16..f5f15d81ea44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -43,6 +43,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SP import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; import android.animation.Animator; +import android.annotation.IntDef; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.Context; @@ -76,6 +77,8 @@ import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.TransitionUtil; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Optional; /** @@ -86,6 +89,18 @@ public class PipTransition extends PipTransitionController { private static final String TAG = PipTransition.class.getSimpleName(); + /** No fixed rotation, or fixed rotation state is undefined. */ + private static final int FIXED_ROTATION_UNDEFINED = 0; + /** + * Fixed rotation detected via callbacks (see PipController#startSwipePipToHome()); + * this is used in the swipe PiP to home case, since the transitions itself isn't supposed to + * see the fixed rotation. + */ + private static final int FIXED_ROTATION_CALLBACK = 1; + + /** Fixed rotation detected in the incoming transition. */ + private static final int FIXED_ROTATION_TRANSITION = 2; + private final Context mContext; private final PipTransitionState mPipTransitionState; private final PipDisplayLayoutState mPipDisplayLayoutState; @@ -106,17 +121,28 @@ public class PipTransition extends PipTransitionController { /** The Task window that is currently in PIP windowing mode. */ @Nullable private WindowContainerToken mCurrentPipTaskToken; - /** Whether display is in fixed rotation. */ - private boolean mInFixedRotation; + + @IntDef(prefix = { "FIXED_ROTATION_" }, value = { + FIXED_ROTATION_UNDEFINED, + FIXED_ROTATION_CALLBACK, + FIXED_ROTATION_TRANSITION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FixedRotationState {} + + /** Fixed rotation state of the display. */ + private @FixedRotationState int mFixedRotationState = FIXED_ROTATION_UNDEFINED; /** * The rotation that the display will apply after expanding PiP to fullscreen. This is only - * meaningful if {@link #mInFixedRotation} is true. + * meaningful if {@link #mFixedRotationState} is {@link #FIXED_ROTATION_TRANSITION}. */ @Surface.Rotation private int mEndFixedRotation; /** Whether the PIP window has fade out for fixed rotation. */ private boolean mHasFadeOut; + private Rect mInitBounds = new Rect(); + /** Used for setting transform to a transaction from animator. */ private final PipAnimationController.PipTransactionHandler mTransactionConsumer = new PipAnimationController.PipTransactionHandler() { @@ -181,8 +207,16 @@ public class PipTransition extends PipTransitionController { @NonNull Transitions.TransitionFinishCallback finishCallback) { final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); - mInFixedRotation = fixedRotationChange != null; - mEndFixedRotation = mInFixedRotation + if (mFixedRotationState == FIXED_ROTATION_TRANSITION) { + // If we are just about to process potential fixed rotation information, + // then fixed rotation state should either be UNDEFINED or CALLBACK. + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "%s: startAnimation() should start with clear fixed rotation state", TAG); + mFixedRotationState = FIXED_ROTATION_UNDEFINED; + } + mFixedRotationState = fixedRotationChange != null + ? FIXED_ROTATION_TRANSITION : mFixedRotationState; + mEndFixedRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; @@ -347,6 +381,10 @@ public class PipTransition extends PipTransitionController { @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT) { + // Transition either finished pre-emptively, got merged, or aborted, + // so update fixed rotation state to default. + mFixedRotationState = FIXED_ROTATION_UNDEFINED; + if (transition != mExitTransition) { return; } @@ -408,7 +446,8 @@ public class PipTransition extends PipTransitionController { // done at the start. But if it is running fixed rotation, there will be a seamless // display transition later. So the last rotation transform needs to be kept to // avoid flickering, and then the display transition will reset the transform. - if (!mInFixedRotation && mFinishTransaction != null) { + if (mFixedRotationState != FIXED_ROTATION_TRANSITION + && mFinishTransaction != null) { mFinishTransaction.merge(tx); } } else { @@ -426,12 +465,27 @@ public class PipTransition extends PipTransitionController { mSurfaceTransactionHelper.crop(tx, leash, destinationBounds) .resetScale(tx, leash, destinationBounds) .round(tx, leash, true /* applyCornerRadius */); + if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) { + // Resetting the scale for pinned task while re-adjusting its crop, + // also scales the overlay. So we need to update the overlay leash too. + Rect overlayBounds = new Rect(destinationBounds); + final int overlaySize = PipContentOverlay.PipAppIconOverlay + .getOverlaySize(mInitBounds, destinationBounds); + + overlayBounds.offsetTo( + (destinationBounds.width() - overlaySize) / 2, + (destinationBounds.height() - overlaySize) / 2); + mSurfaceTransactionHelper.resetScale(tx, + mPipOrganizer.mPipOverlay, overlayBounds); + } } + mInitBounds.setEmpty(); wct.setBoundsChangeTransaction(taskInfo.token, tx); } final int displayRotation = taskInfo.getConfiguration().windowConfiguration .getDisplayRotation(); - if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation + if (enteringPip && mFixedRotationState == FIXED_ROTATION_TRANSITION + && mEndFixedRotation != displayRotation && hasValidLeash) { // Launcher may update the Shelf height during the animation, which will update the // destination bounds. Because this is in fixed rotation, We need to make sure the @@ -451,6 +505,8 @@ public class PipTransition extends PipTransitionController { mFinishTransaction = null; callFinishCallback(wct); } + // This is the end of transition on the Shell side so update the fixed rotation state. + mFixedRotationState = FIXED_ROTATION_UNDEFINED; finishResizeForMenu(destinationBounds); } @@ -467,6 +523,7 @@ public class PipTransition extends PipTransitionController { // mFinishCallback might be null with an outdated mCurrentPipTaskToken // for example, when app crashes while in PiP and exit transition has not started mCurrentPipTaskToken = null; + mFixedRotationState = FIXED_ROTATION_UNDEFINED; if (mFinishCallback == null) return; mFinishCallback.onTransitionFinished(null /* wct */); mFinishCallback = null; @@ -475,6 +532,9 @@ public class PipTransition extends PipTransitionController { @Override public void onFixedRotationStarted() { + if (mFixedRotationState == FIXED_ROTATION_UNDEFINED) { + mFixedRotationState = FIXED_ROTATION_CALLBACK; + } fadeEnteredPipIfNeed(false /* show */); } @@ -555,9 +615,9 @@ public class PipTransition extends PipTransitionController { } } // if overlay is present remove it immediately, as exit transition came before it faded out - if (mPipOrganizer.mSwipePipToHomeOverlay != null) { - startTransaction.remove(mPipOrganizer.mSwipePipToHomeOverlay); - clearSwipePipToHomeOverlay(); + if (mPipOrganizer.mPipOverlay != null) { + startTransaction.remove(mPipOrganizer.mPipOverlay); + clearPipOverlay(); } if (pipChange == null) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, @@ -656,7 +716,7 @@ public class PipTransition extends PipTransitionController { // Check if it is fixed rotation. final int rotationDelta; - if (mInFixedRotation) { + if (mFixedRotationState == FIXED_ROTATION_TRANSITION) { final int startRotation = pipChange.getStartRotation(); final int endRotation = mEndFixedRotation; rotationDelta = deltaRotation(startRotation, endRotation); @@ -873,11 +933,13 @@ public class PipTransition extends PipTransitionController { final int startRotation = pipChange.getStartRotation(); // Check again in case some callers use startEnterAnimation directly so the flag was not // set in startAnimation, e.g. from DefaultMixedHandler. - if (!mInFixedRotation) { + if (mFixedRotationState != FIXED_ROTATION_TRANSITION) { mEndFixedRotation = pipChange.getEndFixedRotation(); - mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED; + mFixedRotationState = mEndFixedRotation != ROTATION_UNDEFINED + ? FIXED_ROTATION_TRANSITION : mFixedRotationState; } - final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation(); + final int endRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION + ? mEndFixedRotation : pipChange.getEndRotation(); setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, taskInfo.topActivityInfo); @@ -888,10 +950,15 @@ public class PipTransition extends PipTransitionController { final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); final Rect currentBounds = pipChange.getStartAbsBounds(); + + // Cache the start bounds for overlay manipulations as a part of finishCallback. + mInitBounds.set(currentBounds); + int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds, destinationBounds); - if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { + if (rotationDelta != Surface.ROTATION_0 + && mFixedRotationState == FIXED_ROTATION_TRANSITION) { // Need to get the bounds of new rotation in old rotation for fixed rotation, computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, destinationBounds, sourceHintRect); @@ -955,10 +1022,12 @@ public class PipTransition extends PipTransitionController { } else { throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); } + mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash(); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration); - if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { + if (rotationDelta != Surface.ROTATION_0 + && mFixedRotationState == FIXED_ROTATION_TRANSITION) { // For fixed rotation, the animation destination bounds is in old rotation coordinates. // Set the destination bounds to new coordinates after the animation is finished. // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation. @@ -997,14 +1066,18 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect, @NonNull Rect destinationBounds, @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) { - if (mInFixedRotation) { + if (mFixedRotationState == FIXED_ROTATION_TRANSITION) { // If rotation changes when returning to home, the transition should contain both the // entering PiP and the display change (PipController#startSwipePipToHome has updated // the display layout to new rotation). So it is not expected to see fixed rotation. ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation); } - final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; + Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds(); + if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) { + mInitBounds.set(appBounds); + } + final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay; if (swipePipToHomeOverlay != null) { // Launcher fade in the overlay on top of the fullscreen Task. It is possible we // reparent the PIP activity to a new PIP task (in case there are other activities @@ -1033,7 +1106,7 @@ public class PipTransition extends PipTransitionController { sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); if (swipePipToHomeOverlay != null) { mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay, - this::clearSwipePipToHomeOverlay /* callback */, false /* withStartDelay */); + this::clearPipOverlay /* callback */, false /* withStartDelay */); } mPipTransitionState.setInSwipePipToHomeTransition(false); } @@ -1177,8 +1250,8 @@ public class PipTransition extends PipTransitionController { mPipMenuController.updateMenuBounds(destinationBounds); } - private void clearSwipePipToHomeOverlay() { - mPipOrganizer.mSwipePipToHomeOverlay = null; + private void clearPipOverlay() { + mPipOrganizer.mPipOverlay = null; } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 20c57fa5e566..04911c0bc064 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -78,9 +78,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { return; } - if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) { - mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay, true /* withStartDelay*/); + if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) { + mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, + null /* callback */, true /* withStartDelay*/); } onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx); sendOnPipTransitionFinished(direction); @@ -90,9 +90,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH public void onPipAnimationCancel(TaskInfo taskInfo, PipAnimationController.PipTransitionAnimator animator) { final int direction = animator.getTransitionDirection(); - if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) { - mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay, true /* withStartDelay */); + if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) { + mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, + null /* callback */, true /* withStartDelay */); } sendOnPipTransitionCancelled(animator.getTransitionDirection()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 0367ba160605..d023cea6d19d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -995,16 +995,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { t.show(mOpeningTasks.get(i).mTaskSurface); } for (int i = 0; i < mPausingTasks.size(); ++i) { - if (!sendUserLeaveHint && mPausingTasks.get(i).isLeaf()) { - // This means recents is not *actually* finishing, so of course we gotta - // do special stuff in WMCore to accommodate. - wct.setDoNotPip(mPausingTasks.get(i).mToken); - } - // Since we will reparent out of the leashes, pre-emptively hide the child - // surface to match the leash. Otherwise, there will be a flicker before the - // visibility gets committed in Core when using split-screen (in splitscreen, - // the leaf-tasks are not "independent" so aren't hidden by normal setup). - t.hide(mPausingTasks.get(i).mTaskSurface); + cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint); + } + for (int i = 0; i < mClosingTasks.size(); ++i) { + cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint); } if (mPipTransaction != null && sendUserLeaveHint) { SurfaceControl pipLeash = null; @@ -1053,6 +1047,20 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } } + private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, + SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) { + if (!sendUserLeaveHint && task.isLeaf()) { + // This means recents is not *actually* finishing, so of course we gotta + // do special stuff in WMCore to accommodate. + wct.setDoNotPip(task.mToken); + } + // Since we will reparent out of the leashes, pre-emptively hide the child + // surface to match the leash. Otherwise, there will be a flicker before the + // visibility gets committed in Core when using split-screen (in splitscreen, + // the leaf-tasks are not "independent" so aren't hidden by normal setup). + finishTransaction.hide(task.mTaskSurface); + } + @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 56f1c784f3a7..7b5709769369 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -847,9 +847,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1)) .orElse(null); if (taskInfo != null) { - startTask(taskInfo.taskId, position, options); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, - "Start task in background"); + if (ENABLE_SHELL_TRANSITIONS) { + mStageCoordinator.startTask(taskInfo.taskId, position, options); + } else { + startTask(taskInfo.taskId, position, options); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background"); return; } if (samePackage(packageName1, packageName2, userId1, userId2)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index be685b57f779..77427d999aaf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -263,6 +263,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mStartIntent2 = startIntent2; mActivatePosition = position; } + SplitRequest(int taskId1, int position) { + mActivateTaskId = taskId1; + mActivatePosition = position; + } SplitRequest(int taskId1, int taskId2, int position) { mActivateTaskId = taskId1; mActivateTaskId2 = taskId2; @@ -556,6 +560,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + /** Use this method to launch an existing Task via a taskId */ + void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { + mSplitRequest = new SplitRequest(taskId, position); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); + wct.startTask(taskId, options); + // If this should be mixed, send the task to avoid split handle transition directly. + if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) { + mTaskOrganizer.applyTransaction(wct); + return; + } + + // If split screen is not activated, we're expecting to open a pair of apps to split. + final int extraTransitType = mMainStage.isActive() + ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; + prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering); + + mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, + extraTransitType, !mIsDropEntering); + } + /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { @@ -1593,7 +1618,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Ensure to evict old splitting tasks because the new split pair might be composed by // one of the splitting tasks, evicting the task when finishing entering transition // won't guarantee to put the task to the indicated new position. - mMainStage.evictAllChildren(wct); + if (!mIsDropEntering) { + mMainStage.evictAllChildren(wct); + } mMainStage.reparentTopTask(wct); prepareSplitLayout(wct, resizeAnim); } @@ -1639,7 +1666,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { - mSplitLayout.update(finishT); + mSplitLayout.update(finishT, true /* resetImePosition */); mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash, getMainStageBounds()); mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash, @@ -1833,9 +1860,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) && mMainStage.isActive()) { // Clear the divider remote animating flag as the divider will be re-rendered to apply - // the new rotation config. + // the new rotation config. Don't reset the IME state since those updates are not in + // sync with task info changes. mIsDividerRemoteAnimating = false; - mSplitLayout.update(null /* t */); + mSplitLayout.update(null /* t */, false /* resetImePosition */); onLayoutSizeChanged(mSplitLayout); } } @@ -2298,7 +2326,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, */ public void updateSurfaces(SurfaceControl.Transaction transaction) { updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false); - mSplitLayout.update(transaction); + mSplitLayout.update(transaction, true /* resetImePosition */); } private void onDisplayChange(int displayId, int fromRotation, int toRotation, @@ -2571,7 +2599,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final TransitionInfo.Change change = info.getChanges().get(iC); if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) { - mSplitLayout.update(startTransaction); + // Don't reset the IME state since those updates are not in sync with the + // display change transition + mSplitLayout.update(startTransaction, false /* resetImePosition */); } if (mMixedHandler.isEnteringPip(change, transitType)) { @@ -2672,7 +2702,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, startTransaction, finishTransaction, finishCallback)) { if (mSplitTransitions.isPendingResize(transition)) { // Only need to update in resize because divider exist before transition. - mSplitLayout.update(startTransaction); + mSplitLayout.update(startTransaction, true /* resetImePosition */); startTransaction.apply(); } return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 43ae5f47c92f..ae21c4bf5450 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -247,7 +247,7 @@ public class SplashscreenContentDrawer { } params.layoutInDisplayCutoutMode = a.getInt( R.styleable.Window_windowLayoutInDisplayCutoutMode, - params.layoutInDisplayCutoutMode); + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS); params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0); a.recycle(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java index e03f82526bdb..34c015f05c68 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java @@ -330,6 +330,11 @@ public class TaskViewTransitions implements Transitions.TransitionHandler { continue; } if (isHide) { + if (pending.mType == TRANSIT_TO_BACK) { + // TO_BACK is only used when setting the task view visibility immediately, + // so in that case we can also hide the surface immediately + startTransaction.hide(chg.getLeash()); + } tv.prepareHideAnimation(finishTransaction); } else { tv.prepareCloseAnimation(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index ce7fef2d1fdf..9f20f49b4094 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -48,9 +48,9 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; @@ -298,7 +298,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; - } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) { + } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) { final WindowContainerTransaction wct = mUnfoldHandler.handleRequest(transition, request); if (wct != null) { @@ -902,6 +902,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return false; } + /** Use to when split use taskId to enter, check if this enter transition should be mixed or + * not.*/ + public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) { + // Check if this intent package is same as pip one or not, if true we want let the pip + // task enter split. + if (mPipHandler != null) { + return mPipHandler.isInPipPackage( + SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer)); + } + return false; + } + /** @return whether the transition-request represents a pip-entry. */ public boolean requestHasPipEnter(TransitionRequestInfo request) { return mPipHandler.requestHasPipEnter(request); 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/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index 07c54293111c..20ff79f7318e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -106,7 +106,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { - if (hasUnfold(info) && transition != mTransition) { + if (shouldPlayUnfoldAnimation(info) && transition != mTransition) { // Take over transition that has unfold, we might receive it if no other handler // accepted request in handleRequest, e.g. for rotation + unfold or // TRANSIT_NONE + unfold transitions @@ -213,14 +213,36 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene } /** Whether `request` contains an unfold action. */ - public boolean hasUnfold(@NonNull TransitionRequestInfo request) { + public boolean shouldPlayUnfoldAnimation(@NonNull TransitionRequestInfo request) { + // Unfold animation won't play when animations are disabled + if (!ValueAnimator.areAnimatorsEnabled()) return false; + return (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null - && request.getDisplayChange().isPhysicalDisplayChanged()); + && isUnfoldDisplayChange(request.getDisplayChange())); + } + + private boolean isUnfoldDisplayChange( + @NonNull TransitionRequestInfo.DisplayChange displayChange) { + if (!displayChange.isPhysicalDisplayChanged()) { + return false; + } + + if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) { + return false; + } + + // Handle only unfolding, currently we don't have an animation when folding + final int endArea = + displayChange.getEndAbsBounds().width() * displayChange.getEndAbsBounds().height(); + final int startArea = displayChange.getStartAbsBounds().width() + * displayChange.getStartAbsBounds().height(); + + return endArea > startArea; } /** Whether `transitionInfo` contains an unfold action. */ - public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) { + public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) { // Unfold animation won't play when animations are disabled if (!ValueAnimator.areAnimatorsEnabled()) return false; @@ -250,7 +272,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - if (hasUnfold(request)) { + if (shouldPlayUnfoldAnimation(request)) { mTransition = transition; return new WindowContainerTransaction(); } 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/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java index 145c8f0ab8af..636c6326d213 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java @@ -69,7 +69,7 @@ public class DividerViewTest extends ShellTestCase { SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager", mContext, configuration, mCallbacks); - splitWindowManager.init(mSplitLayout, new InsetsState()); + splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */); mDividerView = spy((DividerView) splitWindowManager.getDividerView()); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java index 2e5078d86a8b..150aa13f2d00 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java @@ -59,7 +59,7 @@ public class SplitWindowManagerTests extends ShellTestCase { @Test @UiThreadTest public void testInitRelease() { - mSplitWindowManager.init(mSplitLayout, new InsetsState()); + mSplitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */); assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull(); mSplitWindowManager.release(null /* t */); assertThat(mSplitWindowManager.getSurfaceControl()).isNull(); 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/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 99cd4f391153..855b7ee04702 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -235,7 +235,7 @@ public class SplitScreenControllerTests extends ShellTestCase { mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null); - verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), + verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); verify(mSplitScreenController, never()).supportMultiInstancesSplit(any()); verify(mStageCoordinator, never()).switchSplitPosition(any()); @@ -243,7 +243,6 @@ public class SplitScreenControllerTests extends ShellTestCase { @Test public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() { - doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any()); doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = @@ -260,8 +259,8 @@ public class SplitScreenControllerTests extends ShellTestCase { mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null); - - verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), + verify(mSplitScreenController, never()).supportMultiInstancesSplit(any()); + verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); } 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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java index 6d73c12dc304..fc1fe1cd0acc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -98,10 +98,13 @@ public class UnfoldTransitionHandlerTest { } @Test - public void handleRequest_physicalDisplayChange_handlesTransition() { + public void handleRequest_physicalDisplayChangeUnfold_handlesTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( - Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); + Display.DEFAULT_DISPLAY) + .setPhysicalDisplayChanged(true) + .setStartAbsBounds(new Rect(0, 0, 100, 100)) + .setEndAbsBounds(new Rect(0, 0, 200, 200)); TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); @@ -112,6 +115,23 @@ public class UnfoldTransitionHandlerTest { } @Test + public void handleRequest_physicalDisplayChangeFold_doesNotHandleTransition() { + ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); + TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( + Display.DEFAULT_DISPLAY) + .setPhysicalDisplayChanged(true) + .setStartAbsBounds(new Rect(0, 0, 200, 200)) + .setEndAbsBounds(new Rect(0, 0, 100, 100)); + TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, + triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); + + WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition, + requestInfo); + + assertThat(result).isNull(); + } + + @Test public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( @@ -306,7 +326,10 @@ public class UnfoldTransitionHandlerTest { private TransitionRequestInfo createUnfoldTransitionRequestInfo() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( - Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); + Display.DEFAULT_DISPLAY) + .setPhysicalDisplayChanged(true) + .setStartAbsBounds(new Rect(0, 0, 100, 100)) + .setEndAbsBounds(new Rect(0, 0, 200, 200)); return new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); } 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/PointerController.cpp b/libs/input/PointerController.cpp index 65e16056c106..bba9c9764eee 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -218,6 +218,15 @@ void PointerController::setPresentation(Presentation presentation) { mLocked.presentation = presentation; + if (input_flags::enable_pointer_choreographer()) { + // When pointer choreographer is enabled, the presentation mode is only set once when the + // PointerController is constructed, before the display viewport is provided. + // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer + // is permanently enabled. The presentation can be set in the constructor. + mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER); + return; + } + if (!mCursorController.isViewportValid()) { return; } 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/media/OWNERS b/media/OWNERS index 4a6648e91af4..994a7b810009 100644 --- a/media/OWNERS +++ b/media/OWNERS @@ -21,7 +21,6 @@ wonsik@google.com include platform/frameworks/av:/media/janitors/media_solutions_OWNERS # SEO -sungsoo@google.com # SEA/KIR/BVE jtinker@google.com diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS index bbe5e06bb282..058c5be6af6c 100644 --- a/media/java/android/media/OWNERS +++ b/media/java/android/media/OWNERS @@ -2,7 +2,6 @@ fgoldfain@google.com elaurent@google.com lajos@google.com -sungsoo@google.com jmtrivi@google.com # go/android-fwk-media-solutions for info on areas of ownership. diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 3c0b00262c70..07f63e5441af 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -62,3 +62,10 @@ flag { description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users." bug: "288580225" } + +flag { + name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name" + namespace: "media_solutions" + description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos." + bug: "314324170" +} diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 901ea46ba38b..a8ffd2b4dd8f 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -110,6 +110,10 @@ interface ITvInputManager { void pauseRecording(in IBinder sessionToken, in Bundle params, int userId); void resumeRecording(in IBinder sessionToken, in Bundle params, int userId); + // For playback control + void startPlayback(in IBinder sessionToken, int userId); + void stopPlayback(in IBinder sessionToken, int mode, int userId); + // For broadcast info void requestBroadcastInfo(in IBinder sessionToken, in BroadcastInfoRequest request, int userId); void removeBroadcastInfo(in IBinder sessionToken, int id, int userId); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 5246f5c4dc1e..e37ee6e342e5 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -63,6 +63,9 @@ oneway interface ITvInputSession { void timeShiftSetMode(int mode); void timeShiftEnablePositionTracking(boolean enable); + void startPlayback(); + void stopPlayback(int mode); + // For the recording session void startRecording(in Uri programUri, in Bundle params); void stopRecording(); diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index d749b91e3889..ae3ee6535c71 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -79,6 +79,8 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_TIME_SHIFT_SET_MODE = 30; private static final int DO_SET_TV_MESSAGE_ENABLED = 31; private static final int DO_NOTIFY_TV_MESSAGE = 32; + private static final int DO_STOP_PLAYBACK = 33; + private static final int DO_START_PLAYBACK = 34; private final boolean mIsRecordingSession; private final HandlerCaller mCaller; @@ -286,6 +288,14 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2); break; } + case DO_STOP_PLAYBACK: { + mTvInputSessionImpl.stopPlayback(msg.arg1); + break; + } + case DO_START_PLAYBACK: { + mTvInputSessionImpl.startPlayback(); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -483,6 +493,17 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand enabled)); } + @Override + public void stopPlayback(int mode) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_STOP_PLAYBACK, mode)); + } + + @Override + public void startPlayback() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_PLAYBACK)); + } + + private final class TvInputEventReceiver extends InputEventReceiver { TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 631ab9a2693d..c685a5adb08b 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -339,6 +339,14 @@ public final class TvInputManager { */ public static final int VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN = VIDEO_UNAVAILABLE_REASON_END; + /** + * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and + * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because + * it has been stopped by stopPlayback. + * @hide + */ + public static final int VIDEO_UNAVAILABLE_REASON_STOPPED = 19; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({TIME_SHIFT_STATUS_UNKNOWN, TIME_SHIFT_STATUS_UNSUPPORTED, @@ -3302,6 +3310,30 @@ public final class TvInputManager { } } + void stopPlayback(int mode) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.stopPlayback(mToken, mode, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + void startPlayback() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.startPlayback(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Sends TV messages to the service for testing purposes */ diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 720d9a6291de..55fa51755177 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -34,6 +34,7 @@ import android.graphics.Rect; import android.hardware.hdmi.HdmiDeviceInfo; import android.media.AudioPresentation; import android.media.PlaybackParams; +import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -1531,6 +1532,32 @@ public abstract class TvInputService extends Service { } /** + * Called when the application requests playback of the Audio, Video, and CC streams to be + * stopped, but the metadata should continue to be filtered. + * + * <p>The metadata that will continue to be filtered includes the PSI + * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1. + * + * <p> Note that this is different form {@link #timeShiftPause()} as should release the + * stream, making it impossible to resume from this position again. + * @param mode + * @hide + */ + public void onStopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) { + } + + /** + * Starts playback of the Audio, Video, and CC streams. + * + * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be + * used after stopping playback. This is used to restart playback from the current position + * in the live broadcast. + * @hide + */ + public void onStartPlayback() { + } + + /** * Called when the application requests to play a given recorded TV program. * * @param recordedProgramUri The URI of a recorded TV program. @@ -1993,6 +2020,20 @@ public abstract class TvInputService extends Service { } /** + * Calls {@link #onStopPlayback(int)}. + */ + void stopPlayback(int mode) { + onStopPlayback(mode); + } + + /** + * Calls {@link #onStartPlayback()}. + */ + void startPlayback() { + onStartPlayback(); + } + + /** * Calls {@link #onTimeShiftPlay(Uri)}. */ void timeShiftPlay(Uri recordedProgramUri) { diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 196b5c3112c0..233f96675543 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -37,6 +37,7 @@ import android.media.PlaybackParams; import android.media.tv.TvInputManager.Session; import android.media.tv.TvInputManager.Session.FinishedInputEventCallback; import android.media.tv.TvInputManager.SessionCallback; +import android.media.tv.interactive.TvInteractiveAppService; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -643,6 +644,35 @@ public class TvView extends ViewGroup { } } + /** + * Stops playback of the Audio, Video, and CC streams, but continue filtering the metadata. + * + * <p>The metadata that will continue to be filtered includes the PSI + * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1. + * + * <p> Note that this is different form {@link #timeShiftPause()} as this completely drops + * the stream, making it impossible to resume from this position again. + * @hide + */ + public void stopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) { + if (mSession != null) { + mSession.stopPlayback(mode); + } + } + + /** + * Starts playback of the Audio, Video, and CC streams. + * + * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be + * used after stopping playback. This is used to restart playback from the current position + * in the live broadcast. + * @hide + */ + public void startPlayback() { + if (mSession != null) { + mSession.startPlayback(); + } + } /** * Sends TV messages to the session for testing purposes 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/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index 1c8a8d5771d9..f4641b9930cb 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -85,6 +85,8 @@ <!-- [CHAR LIMIT=15] --> <string name="ok">OK</string> + <!-- Confirmation text label for button to archive an application. Archiving means uninstalling the app without deleting user's personal data and replacing the app with a stub app with minimum size. So, the user can unarchive the app later and not lose any personal data. --> + <string name="archive">Archive</string> <!-- [CHAR LIMIT=30] --> <string name="update_anyway">Update anyway</string> <!-- [CHAR LIMIT=15] --> @@ -115,6 +117,16 @@ <!-- [CHAR LIMIT=none] --> <string name="uninstall_application_text">Do you want to uninstall this app?</string> <!-- [CHAR LIMIT=none] --> + <string name="archive_application_text">Your personal data will be saved</string> + <!-- [CHAR LIMIT=none] --> + <string name="archive_application_text_all_users">Archive this app for all users? Your personal data will be saved</string> + <!-- [CHAR LIMIT=none] --> + <string name="archive_application_text_current_user_work_profile">Archive this app on your work profile? Your personal data will be saved</string> + <!-- [CHAR LIMIT=none] --> + <string name="archive_application_text_user">Archive this app for <xliff:g id="username">%1$s</xliff:g>? Your personal data will be saved</string> + <!-- [CHAR LIMIT=none] --> + <string name="archive_application_text_current_user_private_profile">Do you want to archive this app from your private space? Your personal data will be saved</string> + <!-- [CHAR LIMIT=none] --> <string name="uninstall_application_text_all_users">Do you want to uninstall this app for <b>all</b> users? The application and its data will be removed from <b>all</b> users on the device.</string> <!-- [CHAR LIMIT=none] --> @@ -190,11 +202,6 @@ <!-- Dialog attributes to indicate parse errors --> <string name="Parse_error_dlg_text">There was a problem parsing the package.</string> - <!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=30] --> - <string name="wear_not_allowed_dlg_title">Android Wear</string> - <!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=none] --> - <string name="wear_not_allowed_dlg_text">Install/Uninstall actions not supported on Wear.</string> - <!-- Message that the app to be installed is being staged [CHAR LIMIT=50] --> <string name="message_staging">Staging app…</string> @@ -239,6 +246,8 @@ <!-- Label for cloned app in uninstall dialogue [CHAR LIMIT=40] --> <string name="cloned_app_label"><xliff:g id="package_label">%1$s</xliff:g> Clone</string> + <!-- Label for archiving an app in uninstall dialogue --> + <string name="archiving_app_label">Archive <xliff:g id="package_label">%1$s</xliff:g>?</string> <!-- Label for button to continue install of an app whose source cannot be identified [CHAR LIMIT=40] --> <string name="anonymous_source_continue">Continue</string> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index e8890504f622..2da8c8c69ff8 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -130,6 +130,9 @@ public class UninstallAlertDialogFragment extends DialogFragment implements final boolean isUpdate = ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); + final boolean isArchive = + android.content.pm.Flags.archiving() && ( + (dialogInfo.deleteFlags & PackageManager.DELETE_ARCHIVE) != 0); final UserHandle myUserHandle = Process.myUserHandle(); UserManager userManager = getContext().getSystemService(UserManager.class); if (isUpdate) { @@ -140,7 +143,9 @@ public class UninstallAlertDialogFragment extends DialogFragment implements } } else { if (dialogInfo.allUsers && !isSingleUser(userManager)) { - messageBuilder.append(getString(R.string.uninstall_application_text_all_users)); + messageBuilder.append( + isArchive ? getString(R.string.archive_application_text_all_users) + : getString(R.string.uninstall_application_text_all_users)); } else if (!dialogInfo.user.equals(myUserHandle)) { int userId = dialogInfo.user.getIdentifier(); UserManager customUserManager = getContext() @@ -150,9 +155,10 @@ public class UninstallAlertDialogFragment extends DialogFragment implements if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED) && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) { - messageBuilder.append( - getString(R.string.uninstall_application_text_current_user_work_profile, - userName)); + messageBuilder.append(isArchive + ? getString(R.string.archive_application_text_current_user_work_profile) + : getString( + R.string.uninstall_application_text_current_user_work_profile)); } else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE) && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) { mIsClonedApp = true; @@ -161,9 +167,14 @@ public class UninstallAlertDialogFragment extends DialogFragment implements } else if (Flags.allowPrivateProfile() && customUserManager.isPrivateProfile() && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) { - messageBuilder.append(getString( - R.string.uninstall_application_text_current_user_private_profile, - userName)); + messageBuilder.append( + isArchive ? getString( + R.string.archive_application_text_current_user_private_profile) + : getString( + R.string.uninstall_application_text_current_user_private_profile)); + } else if (isArchive) { + messageBuilder.append( + getString(R.string.archive_application_text_user, userName)); } else { messageBuilder.append( getString(R.string.uninstall_application_text_user, userName)); @@ -172,24 +183,27 @@ public class UninstallAlertDialogFragment extends DialogFragment implements mIsClonedApp = true; messageBuilder.append(getString( R.string.uninstall_application_text_current_user_clone_profile)); + } else if (Process.myUserHandle().equals(UserHandle.SYSTEM) + && hasClonedInstance(dialogInfo.appInfo.packageName)) { + messageBuilder.append(getString( + R.string.uninstall_application_text_with_clone_instance, + appLabel)); + } else if (isArchive) { + messageBuilder.append(getString(R.string.archive_application_text)); } else { - if (Process.myUserHandle().equals(UserHandle.SYSTEM) - && hasClonedInstance(dialogInfo.appInfo.packageName)) { - messageBuilder.append(getString( - R.string.uninstall_application_text_with_clone_instance, - appLabel)); - } else { - messageBuilder.append(getString(R.string.uninstall_application_text)); - } + messageBuilder.append(getString(R.string.uninstall_application_text)); } } if (mIsClonedApp) { dialogBuilder.setTitle(getString(R.string.cloned_app_label, appLabel)); + } else if (isArchive) { + dialogBuilder.setTitle(getString(R.string.archiving_app_label, appLabel)); } else { dialogBuilder.setTitle(appLabel); } - dialogBuilder.setPositiveButton(android.R.string.ok, this); + dialogBuilder.setPositiveButton(isArchive ? R.string.archive : android.R.string.ok, + this); dialogBuilder.setNegativeButton(android.R.string.cancel, this); String pkg = dialogInfo.appInfo.packageName; @@ -199,7 +213,7 @@ public class UninstallAlertDialogFragment extends DialogFragment implements PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ARCHIVED_PACKAGES)); - suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData(); + suggestToKeepAppData = pkgInfo.applicationInfo.hasFragileUserData() && !isArchive; } catch (PackageManager.NameNotFoundException e) { Log.e(LOG_TAG, "Cannot check hasFragileUserData for " + pkg, e); suggestToKeepAppData = false; 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/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 60bf48c8b75e..8b136da04405 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.6.0-beta01" + extra["jetpackComposeVersion"] = "1.6.0-beta02" } subprojects { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 90c7d46c3004..f4edb36b5e76 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -48,6 +48,7 @@ import com.android.settingslib.spa.gallery.preference.PreferencePageProvider import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider +import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider @@ -100,6 +101,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { SettingsExposedDropdownMenuCheckBoxProvider, SettingsTextFieldPasswordPageProvider, SearchScaffoldPageProvider, + SuwScaffoldPageProvider, CardPageProvider, CopyablePageProvider, ), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt index 1d897f77011e..6a2e5985a735 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt @@ -43,6 +43,7 @@ import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider +import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider @@ -59,6 +60,7 @@ object HomePageProvider : SettingsPageProvider { OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(), SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt new file mode 100644 index 000000000000..6fc8de3ac49c --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt @@ -0,0 +1,85 @@ +/* + * 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.settingslib.spa.gallery.scaffold + +import android.os.Bundle +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.SignalCellularAlt +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.gallery.R +import com.android.settingslib.spa.widget.illustration.Illustration +import com.android.settingslib.spa.widget.illustration.IllustrationModel +import com.android.settingslib.spa.widget.illustration.ResourceType +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton +import com.android.settingslib.spa.widget.scaffold.SuwScaffold +import com.android.settingslib.spa.widget.ui.SettingsBody + +private const val TITLE = "Sample SuwScaffold" + +object SuwScaffoldPageProvider : SettingsPageProvider { + override val name = "SuwScaffold" + + private val owner = createSettingsPage() + + fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + + @Composable + override fun Page(arguments: Bundle?) { + Page() + } +} + +@Composable +private fun Page() { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = "Connect to mobile network", + actionButton = BottomAppBarButton("Next") {}, + dismissButton = BottomAppBarButton("Cancel") {}, + ) { + Column(Modifier.padding(SettingsDimension.itemPadding)) { + SettingsBody("To add another SIM, download a new eSIM.") + } + Illustration(object : IllustrationModel { + override val resId = R.drawable.accessibility_captioning_banner + override val resourceType = ResourceType.IMAGE + }) + Column(Modifier.padding(SettingsDimension.itemPadding)) { + SettingsBody("To add another SIM, download a new eSIM.") + } + Illustration(object : IllustrationModel { + override val resId = R.drawable.accessibility_captioning_banner + override val resourceType = ResourceType.IMAGE + }) + } +} diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index acd90f3c4f4d..7eccfe5ed508 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { api("androidx.slice:slice-builders:1.1.0-alpha02") api("androidx.slice:slice-core:1.1.0-alpha02") api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.2.0-alpha11") + api("androidx.compose.material3:material3:1.2.0-alpha12") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 5a1120e6c7a1..c143390f269c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -37,11 +37,13 @@ object SettingsDimension { val itemPaddingAround = 8.dp val itemDividerHeight = 32.dp + val iconLarge = 48.dp + /** The size when app icon is displayed in list. */ val appIconItemSize = 32.dp /** The size when app icon is displayed in App info page. */ - val appIconInfoSize = 48.dp + val appIconInfoSize = iconLarge /** The vertical padding for buttons. */ val buttonPaddingVertical = 12.dp diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt new file mode 100644 index 000000000000..354b95ddcbfe --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.scaffold + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.toMediumWeight + +data class BottomAppBarButton( + val text: String, + val onClick: () -> Unit, +) + +@Composable +fun SuwScaffold( + imageVector: ImageVector, + title: String, + actionButton: BottomAppBarButton? = null, + dismissButton: BottomAppBarButton? = null, + content: @Composable ColumnScope.() -> Unit, +) { + ActivityTitle(title) + Scaffold { innerPadding -> + BoxWithConstraints( + Modifier + .padding(innerPadding) + .padding(top = SettingsDimension.itemPaddingAround) + ) { + // Use single column layout in portrait, two columns in landscape. + val useSingleColumn = maxWidth < maxHeight + if (useSingleColumn) { + Column { + Column( + Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + Header(imageVector, title) + content() + } + BottomBar(actionButton, dismissButton) + } + } else { + Column(Modifier.padding(horizontal = SettingsDimension.itemPaddingAround)) { + Row((Modifier.weight(1f))) { + Box(Modifier.weight(1f)) { + Header(imageVector, title) + } + Column( + Modifier + .weight(1f) + .verticalScroll(rememberScrollState())) { + content() + } + } + BottomBar(actionButton, dismissButton) + } + } + } + } +} + +@Composable +private fun Header( + imageVector: ImageVector, + title: String +) { + Column(Modifier.padding(SettingsDimension.itemPadding)) { + Icon( + imageVector = imageVector, + contentDescription = null, + modifier = Modifier + .padding(vertical = SettingsDimension.itemPaddingAround) + .size(SettingsDimension.iconLarge), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = title, + modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingVertical), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.displaySmall, + ) + } +} + +@Composable +private fun BottomBar( + actionButton: BottomAppBarButton?, + dismissButton: BottomAppBarButton?, +) { + Row(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { + dismissButton?.apply { + TextButton(onClick) { + ActionText(text) + } + } + Spacer(modifier = Modifier.weight(1f)) + actionButton?.apply { + Button(onClick) { + ActionText(text) + } + } + } +} + +@Composable +private fun ActionText(text: String) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium.toMediumWeight(), + ) +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt new file mode 100644 index 000000000000..35c9f78ada68 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt @@ -0,0 +1,90 @@ +/* + * 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.settingslib.spa.widget.scaffold + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.SignalCellularAlt +import androidx.compose.material3.Text +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SuwScaffoldTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun suwScaffold_titleIsDisplayed() { + composeTestRule.setContent { + SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) { + Text(text = "AAA") + Text(text = "BBB") + } + } + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun suwScaffold_itemsAreDisplayed() { + composeTestRule.setContent { + SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) { + Text(text = "AAA") + Text(text = "BBB") + } + } + + composeTestRule.onNodeWithText("AAA").assertIsDisplayed() + composeTestRule.onNodeWithText("BBB").assertIsDisplayed() + } + + @Test + fun suwScaffold_actionButtonDisplayed() { + composeTestRule.setContent { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = TITLE, + actionButton = BottomAppBarButton(TEXT) {}, + ) {} + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + @Test + fun suwScaffold_dismissButtonDisplayed() { + composeTestRule.setContent { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = TITLE, + dismissButton = BottomAppBarButton(TEXT) {}, + ) {} + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + private companion object { + const val TITLE = "Title" + const val TEXT = "Text" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt index d0d2dc0083a6..e099f1124bf1 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt @@ -20,13 +20,17 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn +private const val TAG = "BroadcastReceiverFlow" + /** * A [BroadcastReceiver] flow for the given [intentFilter]. */ @@ -39,4 +43,6 @@ fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = ca registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) awaitClose { unregisterReceiver(broadcastReceiver) } +}.catch { e -> + Log.e(TAG, "Error while broadcastReceiverFlow", e) }.conflate().flowOn(Dispatchers.Default) diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt index dfaf3c66ff8d..eef5225aef42 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt @@ -31,8 +31,10 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doThrow import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.stub @RunWith(AndroidJUnit4::class) class BroadcastReceiverFlowTest { @@ -74,6 +76,18 @@ class BroadcastReceiverFlowTest { assertThat(onReceiveIsCalled).isTrue() } + @Test + fun broadcastReceiverFlow_unregisterReceiverThrowException_noCrash() = runBlocking { + context.stub { + on { unregisterReceiver(any()) } doThrow IllegalArgumentException() + } + val flow = context.broadcastReceiverFlow(INTENT_FILTER) + + flow.firstWithTimeoutOrNull() + + assertThat(registeredBroadcastReceiver).isNotNull() + } + private companion object { val INTENT_FILTER = IntentFilter() } 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/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 69b61c74625e..2cb44ec39a23 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -31,7 +31,6 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastSubgroup; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; -import android.bluetooth.BluetoothStatusCodes; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -42,7 +41,6 @@ import android.os.Looper; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; -import android.util.Pair; import androidx.annotation.RequiresApi; @@ -53,15 +51,17 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; /** - * LocalBluetoothLeBroadcast provides an interface between the Settings app - * and the functionality of the local {@link BluetoothLeBroadcast}. - * Use the {@link BluetoothLeBroadcast.Callback} to get the result callback. + * LocalBluetoothLeBroadcast provides an interface between the Settings app and the functionality of + * the local {@link BluetoothLeBroadcast}. Use the {@link BluetoothLeBroadcast.Callback} to get the + * result callback. */ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { private static final String TAG = "LocalBluetoothLeBroadcast"; @@ -74,11 +74,12 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { // Order of this profile in device profiles list private static final int ORDINAL = 1; private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; - private static final Uri[] SETTINGS_URIS = new Uri[]{ - Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO), - Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE), - Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME), - }; + private static final Uri[] SETTINGS_URIS = + new Uri[] { + Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO), + Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE), + Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME), + }; private BluetoothLeBroadcast mServiceBroadcast; private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant; @@ -95,62 +96,82 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { private Executor mExecutor; private ContentResolver mContentResolver; private ContentObserver mSettingsObserver; + // Cached broadcast callbacks being register before service is connected. + private Map<BluetoothLeBroadcast.Callback, Executor> mCachedBroadcastCallbackExecutorMap = + new ConcurrentHashMap<>(); - private final ServiceListener mServiceListener = new ServiceListener() { - @Override - public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (DEBUG) { - Log.d(TAG, "Bluetooth service connected: " + profile); - } - if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && !mIsBroadcastProfileReady) { - mServiceBroadcast = (BluetoothLeBroadcast) proxy; - mIsBroadcastProfileReady = true; - registerServiceCallBack(mExecutor, mBroadcastCallback); - List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata(); - if (!metadata.isEmpty()) { - updateBroadcastInfoFromBroadcastMetadata(metadata.get(0)); + private final ServiceListener mServiceListener = + new ServiceListener() { + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (DEBUG) { + Log.d(TAG, "Bluetooth service connected: " + profile); + } + if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) + && !mIsBroadcastProfileReady) { + mServiceBroadcast = (BluetoothLeBroadcast) proxy; + mIsBroadcastProfileReady = true; + registerServiceCallBack(mExecutor, mBroadcastCallback); + List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata(); + if (!metadata.isEmpty()) { + updateBroadcastInfoFromBroadcastMetadata(metadata.get(0)); + } + registerContentObserver(); + if (DEBUG) { + Log.d( + TAG, + "onServiceConnected: register " + + "mCachedBroadcastCallbackExecutorMap = " + + mCachedBroadcastCallbackExecutorMap); + } + mCachedBroadcastCallbackExecutorMap.forEach( + (callback, executor) -> + registerServiceCallBack(executor, callback)); + } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) + && !mIsBroadcastAssistantProfileReady) { + mIsBroadcastAssistantProfileReady = true; + mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy; + registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback); + } } - registerContentObserver(); - } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) - && !mIsBroadcastAssistantProfileReady) { - mIsBroadcastAssistantProfileReady = true; - mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy; - registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback); - } - } - @Override - public void onServiceDisconnected(int profile) { - if (DEBUG) { - Log.d(TAG, "Bluetooth service disconnected"); - } - if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && mIsBroadcastProfileReady) { - mIsBroadcastProfileReady = false; - unregisterServiceCallBack(mBroadcastCallback); - } - if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) - && mIsBroadcastAssistantProfileReady) { - mIsBroadcastAssistantProfileReady = false; - unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback); - } + @Override + public void onServiceDisconnected(int profile) { + if (DEBUG) { + Log.d(TAG, "Bluetooth service disconnected: " + profile); + } + if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) + && mIsBroadcastProfileReady) { + mIsBroadcastProfileReady = false; + unregisterServiceCallBack(mBroadcastCallback); + mCachedBroadcastCallbackExecutorMap.clear(); + } + if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) + && mIsBroadcastAssistantProfileReady) { + mIsBroadcastAssistantProfileReady = false; + unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback); + } - if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) { - unregisterContentObserver(); - } - } - }; + if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) { + unregisterContentObserver(); + } + } + }; private final BluetoothLeBroadcast.Callback mBroadcastCallback = new BluetoothLeBroadcast.Callback() { @Override public void onBroadcastStarted(int reason, int broadcastId) { if (DEBUG) { - Log.d(TAG, - "onBroadcastStarted(), reason = " + reason + ", broadcastId = " + Log.d( + TAG, + "onBroadcastStarted(), reason = " + + reason + + ", broadcastId = " + broadcastId); } setLatestBroadcastId(broadcastId); - setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true); + setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true); } @Override @@ -161,8 +182,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } @Override - public void onBroadcastMetadataChanged(int broadcastId, - @NonNull BluetoothLeBroadcastMetadata metadata) { + public void onBroadcastMetadataChanged( + int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) { if (DEBUG) { Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId); } @@ -172,8 +193,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { @Override public void onBroadcastStopped(int reason, int broadcastId) { if (DEBUG) { - Log.d(TAG, - "onBroadcastStopped(), reason = " + reason + ", broadcastId = " + Log.d( + TAG, + "onBroadcastStopped(), reason = " + + reason + + ", broadcastId = " + broadcastId); } @@ -191,37 +215,42 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { @Override public void onBroadcastUpdated(int reason, int broadcastId) { if (DEBUG) { - Log.d(TAG, - "onBroadcastUpdated(), reason = " + reason + ", broadcastId = " + Log.d( + TAG, + "onBroadcastUpdated(), reason = " + + reason + + ", broadcastId = " + broadcastId); } setLatestBroadcastId(broadcastId); - setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true); + setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true); } @Override public void onBroadcastUpdateFailed(int reason, int broadcastId) { if (DEBUG) { - Log.d(TAG, - "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = " + Log.d( + TAG, + "onBroadcastUpdateFailed(), reason = " + + reason + + ", broadcastId = " + broadcastId); } } @Override - public void onPlaybackStarted(int reason, int broadcastId) { - } + public void onPlaybackStarted(int reason, int broadcastId) {} @Override - public void onPlaybackStopped(int reason, int broadcastId) { - } + public void onPlaybackStopped(int reason, int broadcastId) {} }; private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = new BluetoothLeBroadcastAssistant.Callback() { @Override - public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, - int reason) {} + public void onSourceAdded( + @NonNull BluetoothDevice sink, int sourceId, int reason) {} + @Override public void onSearchStarted(int reason) {} @@ -238,38 +267,65 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {} @Override - public void onSourceAddFailed(@NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastMetadata source, int reason) {} + public void onSourceAddFailed( + @NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastMetadata source, + int reason) {} @Override - public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId, - int reason) {} + public void onSourceModified( + @NonNull BluetoothDevice sink, int sourceId, int reason) {} @Override - public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId, - int reason) {} + public void onSourceModifyFailed( + @NonNull BluetoothDevice sink, int sourceId, int reason) {} @Override - public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, - int reason) { + public void onSourceRemoved( + @NonNull BluetoothDevice sink, int sourceId, int reason) { if (DEBUG) { - Log.d(TAG, "onSourceRemoved(), sink = " + sink + ", reason = " - + reason + ", sourceId = " + sourceId); + Log.d( + TAG, + "onSourceRemoved(), sink = " + + sink + + ", reason = " + + reason + + ", sourceId = " + + sourceId); } } @Override - public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId, - int reason) { + public void onSourceRemoveFailed( + @NonNull BluetoothDevice sink, int sourceId, int reason) { if (DEBUG) { - Log.d(TAG, "onSourceRemoveFailed(), sink = " + sink + ", reason = " - + reason + ", sourceId = " + sourceId); + Log.d( + TAG, + "onSourceRemoveFailed(), sink = " + + sink + + ", reason = " + + reason + + ", sourceId = " + + sourceId); } } @Override - public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId, - @NonNull BluetoothLeBroadcastReceiveState state) {} + public void onReceiveStateChanged( + @NonNull BluetoothDevice sink, + int sourceId, + @NonNull BluetoothLeBroadcastReceiveState state) { + if (DEBUG) { + Log.d( + TAG, + "onReceiveStateChanged(), sink = " + + sink + + ", sourceId = " + + sourceId + + ", state = " + + state); + } + } }; private class BroadcastSettingsObserver extends ContentObserver { @@ -296,8 +352,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { BluetoothAdapter.getDefaultAdapter() .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST); BluetoothAdapter.getDefaultAdapter() - .getProfileProxy(context, mServiceListener, - BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); + .getProfileProxy( + context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); } /** @@ -312,11 +368,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } String programInfo = getProgramInfo(); if (DEBUG) { - Log.d(TAG, - "startBroadcast: language = " + language + " ,programInfo = " + programInfo); + Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo); } buildContentMetadata(language, programInfo); - mServiceBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata, + mServiceBroadcast.startBroadcast( + mBluetoothLeAudioContentMetadata, (mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null); } @@ -325,7 +381,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } public void setProgramInfo(String programInfo) { - setProgramInfo(programInfo, /*updateContentResolver=*/ true); + setProgramInfo(programInfo, /* updateContentResolver= */ true); } private void setProgramInfo(String programInfo, boolean updateContentResolver) { @@ -344,8 +400,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "mContentResolver is null"); return; } - Settings.Secure.putString(mContentResolver, - Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, programInfo); + Settings.Secure.putString( + mContentResolver, + Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, + programInfo); } } @@ -354,7 +412,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } public void setBroadcastCode(byte[] broadcastCode) { - setBroadcastCode(broadcastCode, /*updateContentResolver=*/ true); + setBroadcastCode(broadcastCode, /* updateContentResolver= */ true); } private void setBroadcastCode(byte[] broadcastCode, boolean updateContentResolver) { @@ -372,7 +430,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "mContentResolver is null"); return; } - Settings.Secure.putString(mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE, + Settings.Secure.putString( + mContentResolver, + Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE, new String(broadcastCode, StandardCharsets.UTF_8)); } } @@ -401,8 +461,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "mContentResolver is null"); return; } - Settings.Secure.putString(mContentResolver, - Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, mAppSourceName); + Settings.Secure.putString( + mContentResolver, + Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, + mAppSourceName); } } @@ -427,10 +489,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { if (mBluetoothLeBroadcastMetadata == null) { final List<BluetoothLeBroadcastMetadata> metadataList = mServiceBroadcast.getAllBroadcastMetadata(); - mBluetoothLeBroadcastMetadata = metadataList.stream() - .filter(i -> i.getBroadcastId() == mBroadcastId) - .findFirst() - .orElse(null); + mBluetoothLeBroadcastMetadata = + metadataList.stream() + .filter(i -> i.getBroadcastId() == mBroadcastId) + .findFirst() + .orElse(null); } return mBluetoothLeBroadcastMetadata; } @@ -440,22 +503,27 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "updateBroadcastInfoFromContentProvider: mContentResolver is null"); return; } - String programInfo = Settings.Secure.getString(mContentResolver, - Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO); + String programInfo = + Settings.Secure.getString( + mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO); if (programInfo == null) { programInfo = getDefaultValueOfProgramInfo(); } - setProgramInfo(programInfo, /*updateContentResolver=*/ false); + setProgramInfo(programInfo, /* updateContentResolver= */ false); - String prefBroadcastCode = Settings.Secure.getString(mContentResolver, - Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE); - byte[] broadcastCode = (prefBroadcastCode == null) ? getDefaultValueOfBroadcastCode() - : prefBroadcastCode.getBytes(StandardCharsets.UTF_8); - setBroadcastCode(broadcastCode, /*updateContentResolver=*/ false); + String prefBroadcastCode = + Settings.Secure.getString( + mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE); + byte[] broadcastCode = + (prefBroadcastCode == null) + ? getDefaultValueOfBroadcastCode() + : prefBroadcastCode.getBytes(StandardCharsets.UTF_8); + setBroadcastCode(broadcastCode, /* updateContentResolver= */ false); - String appSourceName = Settings.Secure.getString(mContentResolver, - Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME); - setAppSourceName(appSourceName, /*updateContentResolver=*/ false); + String appSourceName = + Settings.Secure.getString( + mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME); + setAppSourceName(appSourceName, /* updateContentResolver= */ false); } private void updateBroadcastInfoFromBroadcastMetadata( @@ -474,12 +542,12 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } BluetoothLeAudioContentMetadata contentMetadata = subgroup.get(0).getContentMetadata(); setProgramInfo(contentMetadata.getProgramInfo()); - setAppSourceName(getAppSourceName(), /*updateContentResolver=*/ true); + setAppSourceName(getAppSourceName(), /* updateContentResolver= */ true); } /** - * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system - * calls the corresponding callback {@link BluetoothLeBroadcast.Callback}. + * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system calls + * the corresponding callback {@link BluetoothLeBroadcast.Callback}. */ public void stopLatestBroadcast() { stopBroadcast(mBroadcastId); @@ -511,7 +579,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } String programInfo = getProgramInfo(); if (DEBUG) { - Log.d(TAG, + Log.d( + TAG, "updateBroadcast: language = " + language + " ,programInfo = " + programInfo); } mNewAppSourceName = appSourceName; @@ -519,50 +588,79 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata); } - public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor, + /** + * Register Broadcast Callbacks to track its state and receivers + * + * @param executor Executor object for callback + * @param callback Callback object to be registered + */ + public void registerServiceCallBack( + @NonNull @CallbackExecutor Executor executor, @NonNull BluetoothLeBroadcast.Callback callback) { if (mServiceBroadcast == null) { - Log.d(TAG, "The BluetoothLeBroadcast is null."); + Log.d(TAG, "registerServiceCallBack failed, the BluetoothLeBroadcast is null."); + mCachedBroadcastCallbackExecutorMap.putIfAbsent(callback, executor); return; } - mServiceBroadcast.registerCallback(executor, callback); + try { + mServiceBroadcast.registerCallback(executor, callback); + } catch (IllegalArgumentException e) { + Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage()); + } } /** - * Register Broadcast Assistant Callbacks to track it's state and receivers + * Register Broadcast Assistant Callbacks to track its state and receivers * * @param executor Executor object for callback * @param callback Callback object to be registered */ - public void registerBroadcastAssistantCallback(@NonNull @CallbackExecutor Executor executor, + private void registerBroadcastAssistantCallback( + @NonNull @CallbackExecutor Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback) { if (mServiceBroadcastAssistant == null) { - Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null."); + Log.d( + TAG, + "registerBroadcastAssistantCallback failed, " + + "the BluetoothLeBroadcastAssistant is null."); return; } mServiceBroadcastAssistant.registerCallback(executor, callback); } + /** + * Unregister previously registered Broadcast Callbacks + * + * @param callback Callback object to be unregistered + */ public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) { + mCachedBroadcastCallbackExecutorMap.remove(callback); if (mServiceBroadcast == null) { - Log.d(TAG, "The BluetoothLeBroadcast is null."); + Log.d(TAG, "unregisterServiceCallBack failed, the BluetoothLeBroadcast is null."); return; } - mServiceBroadcast.unregisterCallback(callback); + try { + mServiceBroadcast.unregisterCallback(callback); + } catch (IllegalArgumentException e) { + Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage()); + } } /** - * Unregister previousely registered Broadcast Assistant Callbacks + * Unregister previously registered Broadcast Assistant Callbacks * * @param callback Callback object to be unregistered */ - public void unregisterBroadcastAssistantCallback( + private void unregisterBroadcastAssistantCallback( @NonNull BluetoothLeBroadcastAssistant.Callback callback) { if (mServiceBroadcastAssistant == null) { - Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null."); + Log.d( + TAG, + "unregisterBroadcastAssistantCallback, " + + "the BluetoothLeBroadcastAssistant is null."); return; } @@ -570,8 +668,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } private void buildContentMetadata(String language, String programInfo) { - mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo( - programInfo).build(); + mBluetoothLeAudioContentMetadata = + mBuilder.setLanguage(language).setProgramInfo(programInfo).build(); } public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() { @@ -600,9 +698,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { return true; } - /** - * Not supported since LE Audio Broadcasts do not establish a connection. - */ + /** Not supported since LE Audio Broadcasts do not establish a connection. */ public int getConnectionStatus(BluetoothDevice device) { if (mServiceBroadcast == null) { return BluetoothProfile.STATE_DISCONNECTED; @@ -611,9 +707,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { return mServiceBroadcast.getConnectionState(device); } - /** - * Not supported since LE Audio Broadcasts do not establish a connection. - */ + /** Not supported since LE Audio Broadcasts do not establish a connection. */ public List<BluetoothDevice> getConnectedDevices() { if (mServiceBroadcast == null) { return new ArrayList<BluetoothDevice>(0); @@ -622,8 +716,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { return mServiceBroadcast.getConnectedDevices(); } - public @NonNull - List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() { + /** Get all broadcast metadata. */ + public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() { if (mServiceBroadcast == null) { Log.d(TAG, "The BluetoothLeBroadcast is null."); return Collections.emptyList(); @@ -640,16 +734,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { return !mServiceBroadcast.getAllBroadcastMetadata().isEmpty(); } - /** - * Service does not provide method to get/set policy. - */ + /** Service does not provide method to get/set policy. */ public int getConnectionPolicy(BluetoothDevice device) { return CONNECTION_POLICY_FORBIDDEN; } /** - * Service does not provide "setEnabled" method. Please use {@link #startBroadcast}, - * {@link #stopBroadcast()} or {@link #updateBroadcast(String, String)} + * Service does not provide "setEnabled" method. Please use {@link #startBroadcast}, {@link + * #stopBroadcast()} or {@link #updateBroadcast(String, String)} */ public boolean setEnabled(BluetoothDevice device, boolean enabled) { return false; @@ -683,9 +775,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } if (mServiceBroadcast != null) { try { - BluetoothAdapter.getDefaultAdapter().closeProfileProxy( - BluetoothProfile.LE_AUDIO_BROADCAST, - mServiceBroadcast); + BluetoothAdapter.getDefaultAdapter() + .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST, mServiceBroadcast); mServiceBroadcast = null; } catch (Throwable t) { Log.w(TAG, "Error cleaning up LeAudio proxy", t); @@ -694,13 +785,13 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } private String getDefaultValueOfProgramInfo() { - //set the default value; + // set the default value; int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX); return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix; } private byte[] getDefaultValueOfBroadcastCode() { - //set the default value; + // set the default value; return generateRandomPassword().getBytes(StandardCharsets.UTF_8); } @@ -708,14 +799,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { if (DEBUG) { Log.d(TAG, "resetCacheInfo:"); } - setAppSourceName("", /*updateContentResolver=*/ true); + setAppSourceName("", /* updateContentResolver= */ true); mBluetoothLeBroadcastMetadata = null; mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; } private String generateRandomPassword() { String randomUUID = UUID.randomUUID().toString(); - //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx return randomUUID.substring(0, 8) + randomUUID.substring(9, 13); } @@ -752,5 +843,4 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { } } } - } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java index bb103b8896fd..34008ac56042 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java @@ -39,13 +39,14 @@ import com.android.settingslib.R; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; - /** - * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app - * and the functionality of the local {@link BluetoothLeBroadcastAssistant}. - * Use the {@link BluetoothLeBroadcastAssistant.Callback} to get the result callback. + * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app and the + * functionality of the local {@link BluetoothLeBroadcastAssistant}. Use the {@link + * BluetoothLeBroadcastAssistant.Callback} to get the result callback. */ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile { private static final String TAG = "LocalBluetoothLeBroadcastAssistant"; @@ -62,58 +63,76 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; private BluetoothLeBroadcastMetadata.Builder mBuilder; private boolean mIsProfileReady; + // Cached assistant callbacks being register before service is connected. + private final Map<BluetoothLeBroadcastAssistant.Callback, Executor> mCachedCallbackExecutorMap = + new ConcurrentHashMap<>(); + + private final ServiceListener mServiceListener = + new ServiceListener() { + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (DEBUG) { + Log.d(TAG, "Bluetooth service connected"); + } + mService = (BluetoothLeBroadcastAssistant) proxy; + // We just bound to the service, so refresh the UI for any connected LeAudio + // devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + if (DEBUG) { + Log.d( + TAG, + "LocalBluetoothLeBroadcastAssistant found new device: " + + nextDevice); + } + device = mDeviceManager.addDevice(nextDevice); + } + device.onProfileStateChanged( + LocalBluetoothLeBroadcastAssistant.this, + BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } - private final ServiceListener mServiceListener = new ServiceListener() { - @Override - public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (DEBUG) { - Log.d(TAG, "Bluetooth service connected"); - } - mService = (BluetoothLeBroadcastAssistant) proxy; - // We just bound to the service, so refresh the UI for any connected LeAudio devices. - List<BluetoothDevice> deviceList = mService.getConnectedDevices(); - while (!deviceList.isEmpty()) { - BluetoothDevice nextDevice = deviceList.remove(0); - CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); - // we may add a new device here, but generally this should not happen - if (device == null) { + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady = true; if (DEBUG) { - Log.d(TAG, "LocalBluetoothLeBroadcastAssistant found new device: " - + nextDevice); + Log.d( + TAG, + "onServiceConnected, register mCachedCallbackExecutorMap = " + + mCachedCallbackExecutorMap); } - device = mDeviceManager.addDevice(nextDevice); + mCachedCallbackExecutorMap.forEach( + (callback, executor) -> registerServiceCallBack(executor, callback)); } - device.onProfileStateChanged(LocalBluetoothLeBroadcastAssistant.this, - BluetoothProfile.STATE_CONNECTED); - device.refresh(); - } - mProfileManager.callServiceConnectedListeners(); - mIsProfileReady = true; - } - - @Override - public void onServiceDisconnected(int profile) { - if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) { - Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT"); - return; - } - if (DEBUG) { - Log.d(TAG, "Bluetooth service disconnected"); - } - mProfileManager.callServiceDisconnectedListeners(); - mIsProfileReady = false; - } - }; + @Override + public void onServiceDisconnected(int profile) { + if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) { + Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT"); + return; + } + if (DEBUG) { + Log.d(TAG, "Bluetooth service disconnected"); + } + mProfileManager.callServiceDisconnectedListeners(); + mIsProfileReady = false; + mCachedCallbackExecutorMap.clear(); + } + }; - public LocalBluetoothLeBroadcastAssistant(Context context, + public LocalBluetoothLeBroadcastAssistant( + Context context, CachedBluetoothDeviceManager deviceManager, LocalBluetoothProfileManager profileManager) { mProfileManager = profileManager; mDeviceManager = deviceManager; - BluetoothAdapter.getDefaultAdapter(). - getProfileProxy(context, mServiceListener, - BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); + BluetoothAdapter.getDefaultAdapter() + .getProfileProxy( + context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); mBuilder = new BluetoothLeBroadcastMetadata.Builder(); } @@ -123,11 +142,11 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile * @param sink Broadcast Sink to which the Broadcast Source should be added * @param metadata Broadcast Source metadata to be added to the Broadcast Sink * @param isGroupOp {@code true} if Application wants to perform this operation for all - * coordinated set members throughout this session. Otherwise, caller - * would have to add, modify, and remove individual set members. + * coordinated set members throughout this session. Otherwise, caller would have to add, + * modify, and remove individual set members. */ - public void addSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, - boolean isGroupOp) { + public void addSource( + BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) { if (mService == null) { Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); return; @@ -140,36 +159,55 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile * the qr code string. * * @param sink Broadcast Sink to which the Broadcast Source should be added - * @param sourceAddressType hardware MAC Address of the device. See - * {@link BluetoothDevice.AddressType}. + * @param sourceAddressType hardware MAC Address of the device. See {@link + * BluetoothDevice.AddressType}. * @param presentationDelayMicros presentation delay of this Broadcast Source in microseconds. * @param sourceAdvertisingSid 1-byte long Advertising_SID of the Broadcast Source. * @param broadcastId 3-byte long Broadcast_ID of the Broadcast Source. - * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source, - * {@link BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if - * unknown. + * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source, {@link + * BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if unknown. * @param isEncrypted whether the Broadcast Source is encrypted. * @param broadcastCode Broadcast Code for this Broadcast Source, null if code is not required. * @param sourceDevice source advertiser address. * @param isGroupOp {@code true} if Application wants to perform this operation for all - * coordinated set members throughout this session. Otherwise, caller - * would have to add, modify, and remove individual set members. + * coordinated set members throughout this session. Otherwise, caller would have to add, + * modify, and remove individual set members. */ - public void addSource(@NonNull BluetoothDevice sink, int sourceAddressType, - int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId, - int paSyncInterval, boolean isEncrypted, byte[] broadcastCode, - BluetoothDevice sourceDevice, boolean isGroupOp) { + public void addSource( + @NonNull BluetoothDevice sink, + int sourceAddressType, + int presentationDelayMicros, + int sourceAdvertisingSid, + int broadcastId, + int paSyncInterval, + boolean isEncrypted, + byte[] broadcastCode, + BluetoothDevice sourceDevice, + boolean isGroupOp) { if (DEBUG) { Log.d(TAG, "addSource()"); } - buildMetadata(sourceAddressType, presentationDelayMicros, sourceAdvertisingSid, broadcastId, - paSyncInterval, isEncrypted, broadcastCode, sourceDevice); + buildMetadata( + sourceAddressType, + presentationDelayMicros, + sourceAdvertisingSid, + broadcastId, + paSyncInterval, + isEncrypted, + broadcastCode, + sourceDevice); addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp); } - private void buildMetadata(int sourceAddressType, int presentationDelayMicros, - int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted, - byte[] broadcastCode, BluetoothDevice sourceDevice) { + private void buildMetadata( + int sourceAddressType, + int presentationDelayMicros, + int sourceAdvertisingSid, + int broadcastId, + int paSyncInterval, + boolean isEncrypted, + byte[] broadcastCode, + BluetoothDevice sourceDevice) { mBluetoothLeBroadcastMetadata = mBuilder.setSourceDevice(sourceDevice, sourceAddressType) .setSourceAdvertisingSid(sourceAdvertisingSid) @@ -223,10 +261,10 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile /** * Stops an ongoing search for nearby Broadcast Sources. * - * On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be - * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. - * On failure, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be - * called with reason code + * <p>On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be + * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. On failure, + * {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be called with + * reason code * * @throws IllegalStateException if callback was not registered */ @@ -245,8 +283,8 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile * Get information about all Broadcast Sources that a Broadcast Sink knows about. * * @param sink Broadcast Sink from which to get all Broadcast Sources - * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} - * stored in the Broadcast Sink + * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} stored + * in the Broadcast Sink * @throws NullPointerException when <var>sink</var> is null */ public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources( @@ -261,24 +299,50 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile return mService.getAllSources(sink); } - public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor, + /** + * Register Broadcast Assistant Callbacks to track its state and receivers + * + * @param executor Executor object for callback + * @param callback Callback object to be registered + */ + public void registerServiceCallBack( + @NonNull @CallbackExecutor Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback) { if (mService == null) { - Log.d(TAG, "The BluetoothLeBroadcast is null."); + Log.d( + TAG, + "registerServiceCallBack failed, the BluetoothLeBroadcastAssistant is null."); + mCachedCallbackExecutorMap.putIfAbsent(callback, executor); return; } - mService.registerCallback(executor, callback); + try { + mService.registerCallback(executor, callback); + } catch (IllegalArgumentException e) { + Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage()); + } } + /** + * Unregister previously registered Broadcast Assistant Callbacks + * + * @param callback Callback object to be unregistered + */ public void unregisterServiceCallBack( @NonNull BluetoothLeBroadcastAssistant.Callback callback) { + mCachedCallbackExecutorMap.remove(callback); if (mService == null) { - Log.d(TAG, "The BluetoothLeBroadcast is null."); + Log.d( + TAG, + "unregisterServiceCallBack failed, the BluetoothLeBroadcastAssistant is null."); return; } - mService.unregisterCallback(callback); + try { + mService.unregisterCallback(callback); + } catch (IllegalArgumentException e) { + Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage()); + } } public boolean isProfileReady() { @@ -310,9 +374,11 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile return new ArrayList<BluetoothDevice>(0); } return mService.getDevicesMatchingConnectionStates( - new int[]{BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}); + new int[] { + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING + }); } public boolean isEnabled(BluetoothDevice device) { @@ -373,9 +439,8 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile } if (mService != null) { try { - BluetoothAdapter.getDefaultAdapter().closeProfileProxy( - BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, - mService); + BluetoothAdapter.getDefaultAdapter() + .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mService); mService = null; } catch (Throwable t) { Log.w(TAG, "Error cleaning up LeAudio proxy", t); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java index 25833b3ddbba..5aee8cdb8b9e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java @@ -16,7 +16,7 @@ package com.android.settingslib.core.instrumentation; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_TOGGLE; +import static com.android.internal.jank.Cuj.CUJ_SETTINGS_TOGGLE; import static com.android.settingslib.core.instrumentation.SettingsJankMonitor.MONITORED_ANIMATION_DURATION_MS; import static com.google.common.truth.Truth.assertThat; @@ -34,8 +34,8 @@ import androidx.preference.PreferenceGroupAdapter; import androidx.preference.SwitchPreference; import androidx.recyclerview.widget.RecyclerView; +import com.android.internal.jank.Cuj.CujType; import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.jank.InteractionJankMonitor.CujType; import com.android.settingslib.testutils.OverpoweredReflectionHelper; import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor; diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 5c09b1692453..ec456e09a3c1 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -73,6 +73,7 @@ public class SecureSettings { Settings.Secure.TTS_ENABLED_PLUGINS, Settings.Secure.TTS_DEFAULT_LOCALE, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, + Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS, Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, // moved to global Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, // moved to global Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, // moved to global @@ -105,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 b0169a115ec5..5ad14ceb31cf 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -119,6 +119,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.TTS_ENABLED_PLUGINS, new PackageNameListValidator(" ")); VALIDATORS.put(Secure.TTS_DEFAULT_LOCALE, TTS_LIST_VALIDATOR); VALIDATORS.put(Secure.SHOW_IME_WITH_HARD_KEYBOARD, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACCESSIBILITY_BOUNCE_KEYS, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, NON_NEGATIVE_INTEGER_VALIDATOR); @@ -319,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/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 7cec99d4189f..8f459c647316 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -95,7 +95,9 @@ final class SettingsState { static final int SETTINGS_VERSION_NEW_ENCODING = 121; + // LINT.IfChange public static final int MAX_LENGTH_PER_STRING = 32768; + // LINT.ThenChange(/services/core/java/com/android/server/audio/AudioDeviceInventory.java:settings_max_length_per_string) private static final long WRITE_SETTINGS_DELAY_MILLIS = 200; private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000; 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/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 187d0739b734..168039ed5d3d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -33,8 +33,8 @@ import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS import com.android.app.animation.Interpolators +import com.android.internal.jank.Cuj.CujType import com.android.internal.jank.InteractionJankMonitor -import com.android.internal.jank.InteractionJankMonitor.CujType import com.android.systemui.util.maybeForceFullscreen import com.android.systemui.util.registerAnimationOnBackInvoked import kotlin.math.roundToInt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index af35ea44322f..b738e2bc972b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -33,6 +33,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay import android.widget.FrameLayout +import com.android.internal.jank.Cuj.CujType import com.android.internal.jank.InteractionJankMonitor import java.util.LinkedList import kotlin.math.min @@ -58,7 +59,7 @@ constructor( /** The view that will be ghosted and from which the background will be extracted. */ private val ghostedView: View, - /** The [InteractionJankMonitor.CujType] associated to this animation. */ + /** The [CujType] associated to this animation. */ private val cujType: Int? = null, private var interactionJankMonitor: InteractionJankMonitor = InteractionJankMonitor.getInstance(), diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 914e5f2c17bf..fd04b5ee0d9c 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -51,6 +51,7 @@ object ComposeFacade : BaseComposeFacade { activity: ComponentActivity, viewModel: BaseCommunalViewModel, onOpenWidgetPicker: () -> Unit, + onEditDone: () -> Unit, ) { throwComposeUnavailableError() } diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index 59bd95bd9027..5055ee1d73f6 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -66,12 +66,14 @@ object ComposeFacade : BaseComposeFacade { activity: ComponentActivity, viewModel: BaseCommunalViewModel, onOpenWidgetPicker: () -> Unit, + onEditDone: () -> Unit, ) { activity.setContent { PlatformTheme { CommunalHub( viewModel = viewModel, onOpenWidgetPicker = onOpenWidgetPicker, + onEditDone = onEditDone, ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index 1a653c316db1..1d7c7d636158 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationApi::class) + package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog @@ -29,18 +31,17 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown @@ -51,6 +52,7 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -78,6 +80,7 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf +import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel @@ -100,37 +103,41 @@ fun BouncerContent( dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { - val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) - when (layout) { - BouncerSceneLayout.STANDARD -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = modifier, - ) - BouncerSceneLayout.SIDE_BY_SIDE -> - SideBySideLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, - modifier = modifier, - ) - BouncerSceneLayout.STACKED -> - StackedLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, - modifier = modifier, - ) - BouncerSceneLayout.SPLIT -> - SplitLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = modifier, - ) + Box( + // Allows the content within each of the layouts to react to the appearance and + // disappearance of the IME, which is also known as the software keyboard. + // + // Despite the keyboard only being part of the password bouncer, adding it at this level is + // both necessary to properly handle the keyboard in all layouts and harmless in cases when + // the keyboard isn't used (like the PIN or pattern auth methods). + modifier = modifier.imePadding(), + ) { + when (layout) { + BouncerSceneLayout.STANDARD_BOUNCER -> + StandardLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.BESIDE_USER_SWITCHER -> + BesideUserSwitcherLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.BELOW_USER_SWITCHER -> + BelowUserSwitcherLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.SPLIT_BOUNCER -> + SplitLayout( + viewModel = viewModel, + ) + } + + Dialog( + viewModel = viewModel, + dialogFactory = dialogFactory, + ) } } @@ -141,15 +148,330 @@ fun BouncerContent( @Composable private fun StandardLayout( viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, - layout: BouncerSceneLayout = BouncerSceneLayout.STANDARD, - outputOnly: Boolean = false, +) { + val isHeightExpanded = + LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded + + FoldAware( + modifier = + modifier.padding( + top = 92.dp, + bottom = 48.dp, + ), + viewModel = viewModel, + aboveFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + StatusMessage( + viewModel = viewModel, + modifier = Modifier, + ) + + OutputArea( + viewModel = viewModel, + modifier = Modifier.padding(top = if (isHeightExpanded) 96.dp else 64.dp), + ) + } + }, + belowFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + Box( + modifier = Modifier.weight(1f), + ) { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = false, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + }, + ) +} + +/** + * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable + * by double-tapping on the side. + */ +@Composable +private fun SplitLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val authMethod by viewModel.authMethodViewModel.collectAsState() + + Row( + modifier = + modifier + .fillMaxHeight() + .padding( + horizontal = 24.dp, + vertical = if (authMethod is PasswordBouncerViewModel) 24.dp else 48.dp, + ), + ) { + // Left side (in left-to-right locales). + Box( + modifier = Modifier.fillMaxHeight().weight(1f), + ) { + when (authMethod) { + is PinBouncerViewModel -> { + StatusMessage( + viewModel = viewModel, + modifier = Modifier.align(Alignment.TopCenter), + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.align(Alignment.Center)) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter).padding(top = 48.dp), + ) + } + is PatternBouncerViewModel -> { + StatusMessage( + viewModel = viewModel, + modifier = Modifier.align(Alignment.TopCenter), + ) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter).padding(vertical = 48.dp), + ) + } + is PasswordBouncerViewModel -> { + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + else -> Unit + } + } + + // Right side (in right-to-left locales). + Box( + modifier = Modifier.fillMaxHeight().weight(1f), + ) { + when (authMethod) { + is PinBouncerViewModel, + is PatternBouncerViewModel -> { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 8.dp, + centerPatternDotsVertically = true, + modifier = Modifier.align(Alignment.Center), + ) + } + is PasswordBouncerViewModel -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth().align(Alignment.Center), + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + } + } + else -> Unit + } + } + } +} + +/** + * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap + * anywhere on the background to flip their positions. + */ +@Composable +private fun BesideUserSwitcherLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val layoutDirection = LocalLayoutDirection.current + val isLeftToRight = layoutDirection == LayoutDirection.Ltr + val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } + val isHeightExpanded = + LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded + val authMethod by viewModel.authMethodViewModel.collectAsState() + + Row( + modifier = + modifier + .pointerInput(Unit) { + detectTapGestures( + onDoubleTap = { offset -> + // Depending on where the user double tapped, switch the elements such + // that the non-swapped element is closer to the side that was double + // tapped. + setSwapped(offset.x < size.width / 2) + } + ) + } + .padding( + top = if (isHeightExpanded) 128.dp else 96.dp, + bottom = if (isHeightExpanded) 128.dp else 48.dp, + ), + ) { + val animatedOffset by + animateFloatAsState( + targetValue = + if (!isSwapped) { + // A non-swapped element has its natural placement so it's not offset. + 0f + } else if (isLeftToRight) { + // A swapped element has its elements offset horizontally. In the case of + // LTR locales, this means pushing the element to the right, hence the + // positive number. + 1f + } else { + // A swapped element has its elements offset horizontally. In the case of + // RTL locales, this means pushing the element to the left, hence the + // negative number. + -1f + }, + label = "offset", + ) + + fun Modifier.swappable(inversed: Boolean = false): Modifier { + return graphicsLayer { + translationX = + size.width * + animatedOffset * + if (inversed) { + // A negative sign is used to make sure this is offset in the direction + // that's opposite to the direction that the user switcher is pushed in. + -1 + } else { + 1 + } + alpha = animatedAlpha(animatedOffset) + } + } + + UserSwitcher( + viewModel = viewModel, + modifier = Modifier.weight(1f).swappable(), + ) + + FoldAware( + modifier = Modifier.weight(1f).swappable(inversed = true), + viewModel = viewModel, + aboveFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + } + }, + belowFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + val isOutputAreaVisible = authMethod !is PatternBouncerViewModel + // If there is an output area and the window is not tall enough, spacing needs + // to be added between the input and the output areas (otherwise the two get + // very squished together). + val addSpacingBetweenOutputAndInput = isOutputAreaVisible && !isHeightExpanded + + Box( + modifier = + Modifier.weight(1f) + .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp), + ) { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = true, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + }, + ) + } +} + +/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ +@Composable +private fun BelowUserSwitcherLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + Column( + modifier = + modifier.padding( + vertical = 128.dp, + ) + ) { + UserSwitcher( + viewModel = viewModel, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(Modifier.weight(1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = true, + modifier = Modifier.padding(top = 128.dp), + ) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + } + } +} + +@Composable +private fun FoldAware( + viewModel: BouncerViewModel, + aboveFold: @Composable BoxScope.() -> Unit, + belowFold: @Composable BoxScope.() -> Unit, + modifier: Modifier = Modifier, ) { val foldPosture: FoldPosture by foldPosture() val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() - val isSplitAroundTheFold = - foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired + val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired val currentSceneKey = if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey @@ -160,115 +482,57 @@ private fun StandardLayout( modifier = modifier, ) { scene(SceneKeys.ContiguousSceneKey) { - FoldSplittable( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = layout, - outputOnly = outputOnly, + FoldableScene( + aboveFold = aboveFold, + belowFold = belowFold, isSplit = false, ) } scene(SceneKeys.SplitSceneKey) { - FoldSplittable( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = layout, - outputOnly = outputOnly, + FoldableScene( + aboveFold = aboveFold, + belowFold = belowFold, isSplit = true, ) } } } -/** - * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user - * switcher UI) and laid out vertically, centered horizontally. - * - * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't - * render across the location of the fold hardware when the device is fully or part-way unfolded - * with the fold hinge in a horizontal position. - * - * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN - * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter - * their PIN or pattern. - */ @Composable -private fun SceneScope.FoldSplittable( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - layout: BouncerSceneLayout, - outputOnly: Boolean, +private fun SceneScope.FoldableScene( + aboveFold: @Composable BoxScope.() -> Unit, + belowFold: @Composable BoxScope.() -> Unit, isSplit: Boolean, modifier: Modifier = Modifier, ) { - val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() - val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() - var dialog: Dialog? by remember { mutableStateOf(null) } - val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() val splitRatio = LocalContext.current.resources.getFloat( R.dimen.motion_layout_half_fold_bouncer_height_ratio ) - Column(modifier = modifier.padding(horizontal = 32.dp)) { + Column( + modifier = modifier.fillMaxHeight(), + ) { // Content above the fold, when split on a foldable device in a "table top" posture: Box( modifier = Modifier.element(SceneElements.AboveFold) - .fillMaxWidth() .then( if (isSplit) { Modifier.weight(splitRatio) - } else if (outputOnly) { - Modifier.fillMaxHeight() } else { Modifier } ), ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth().padding(top = layout.topPadding), - ) { - Crossfade( - targetState = message, - label = "Bouncer message", - animationSpec = if (message.isUpdateAnimated) tween() else snap(), - ) { message -> - Text( - text = message.text, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - ) - } - - if (!outputOnly) { - Spacer(Modifier.height(layout.spacingBetweenMessageAndEnteredInput)) - - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.OUTPUT_ONLY, - layout = layout, - ) - } - } - - if (outputOnly) { - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.OUTPUT_ONLY, - layout = layout, - modifier = Modifier.align(Alignment.Center), - ) - } + aboveFold() } // Content below the fold, when split on a foldable device in a "table top" posture: Box( modifier = Modifier.element(SceneElements.BelowFold) - .fillMaxWidth() .weight( if (isSplit) { 1 - splitRatio @@ -277,73 +541,40 @@ private fun SceneScope.FoldSplittable( } ), ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize() - ) { - if (!outputOnly) { - Box(Modifier.weight(1f)) { - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.INPUT_ONLY, - layout = layout, - modifier = Modifier.align(Alignment.BottomCenter), - ) - } - } - - Spacer(Modifier.height(48.dp)) - - val actionButtonModifier = Modifier.height(56.dp) - - actionButton.let { actionButtonViewModel -> - if (actionButtonViewModel != null) { - BouncerActionButton( - viewModel = actionButtonViewModel, - modifier = actionButtonModifier, - ) - } else { - Spacer(modifier = actionButtonModifier) - } - } - - Spacer(Modifier.height(layout.bottomPadding)) - } + belowFold() } + } +} - if (dialogMessage != null) { - if (dialog == null) { - dialog = - dialogFactory().apply { - setMessage(dialogMessage) - setButton( - DialogInterface.BUTTON_NEUTRAL, - context.getString(R.string.ok), - ) { _, _ -> - viewModel.onThrottlingDialogDismissed() - } - setCancelable(false) - setCanceledOnTouchOutside(false) - show() - } - } - } else { - dialog?.dismiss() - dialog = null - } +@Composable +private fun StatusMessage( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() + + Crossfade( + targetState = message, + label = "Bouncer message", + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + modifier = modifier, + ) { + Text( + text = it.text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) } } /** - * Renders the user input area, where the user interacts with the UI to enter their credentials. + * Renders the user output area, where the user sees what they entered. * - * For example, this can be the pattern input area, the password text box, or pin pad. + * For example, this can be the PIN shapes or password text field. */ @Composable -private fun UserInputArea( +private fun OutputArea( viewModel: BouncerViewModel, - visibility: UserInputAreaVisibility, - layout: BouncerSceneLayout, modifier: Modifier = Modifier, ) { val authMethodViewModel: AuthMethodBouncerViewModel? by @@ -351,66 +582,115 @@ private fun UserInputArea( when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> - when (visibility) { - UserInputAreaVisibility.OUTPUT_ONLY -> - PinInputDisplay( - viewModel = nonNullViewModel, - modifier = modifier, - ) - UserInputAreaVisibility.INPUT_ONLY -> - PinPad( - viewModel = nonNullViewModel, - layout = layout, - modifier = modifier, - ) - } + PinInputDisplay( + viewModel = nonNullViewModel, + modifier = modifier, + ) is PasswordBouncerViewModel -> - if (visibility == UserInputAreaVisibility.INPUT_ONLY) { - PasswordBouncer( - viewModel = nonNullViewModel, - modifier = modifier, - ) - } - is PatternBouncerViewModel -> - if (visibility == UserInputAreaVisibility.INPUT_ONLY) { - PatternBouncer( - viewModel = nonNullViewModel, - layout = layout, - modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false), - ) - } + PasswordBouncer( + viewModel = nonNullViewModel, + modifier = modifier, + ) else -> Unit } } /** - * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call. + * Renders the user input area, where the user enters their credentials. + * + * For example, this can be the pattern input area or the PIN pad. */ -@OptIn(ExperimentalFoundationApi::class) @Composable -private fun BouncerActionButton( - viewModel: BouncerActionButtonModel, +private fun InputArea( + viewModel: BouncerViewModel, + pinButtonRowVerticalSpacing: Dp, + centerPatternDotsVertically: Boolean, modifier: Modifier = Modifier, ) { - Button( - onClick = viewModel.onClick, - modifier = - modifier.thenIf(viewModel.onLongClick != null) { - Modifier.combinedClickable( - onClick = viewModel.onClick, - onLongClick = viewModel.onLongClick, + val authMethodViewModel: AuthMethodBouncerViewModel? by + viewModel.authMethodViewModel.collectAsState() + + when (val nonNullViewModel = authMethodViewModel) { + is PinBouncerViewModel -> { + PinPad( + viewModel = nonNullViewModel, + verticalSpacing = pinButtonRowVerticalSpacing, + modifier = modifier, + ) + } + is PatternBouncerViewModel -> { + PatternBouncer( + viewModel = nonNullViewModel, + centerDotsVertically = centerPatternDotsVertically, + modifier = modifier, + ) + } + else -> Unit + } +} + +@Composable +private fun ActionArea( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + + actionButton?.let { actionButtonViewModel -> + Box( + modifier = modifier, + ) { + Button( + onClick = actionButtonViewModel.onClick, + modifier = + Modifier.height(56.dp).thenIf(actionButtonViewModel.onLongClick != null) { + Modifier.combinedClickable( + onClick = actionButtonViewModel.onClick, + onLongClick = actionButtonViewModel.onLongClick, + ) + }, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer, + ), + ) { + Text( + text = actionButtonViewModel.label, + style = MaterialTheme.typography.bodyMedium, ) - }, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer, - ), - ) { - Text( - text = viewModel.label, - style = MaterialTheme.typography.bodyMedium, - ) + } + } + } +} + +@Composable +private fun Dialog( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, +) { + val dialogMessage: String? by viewModel.dialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } + + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onDialogDismissed() + } + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } + } + } else { + dialog?.dismiss() + dialog = null } } @@ -420,6 +700,14 @@ private fun UserSwitcher( viewModel: BouncerViewModel, modifier: Modifier = Modifier, ) { + if (!viewModel.isUserSwitcherVisible) { + // Take up the same space as the user switcher normally would, but with nothing inside it. + Box( + modifier = modifier, + ) + return + } + val selectedUserImage by viewModel.selectedUserImage.collectAsState(null) val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList()) @@ -539,195 +827,10 @@ private fun UserSwitcherDropdownMenu( } } -/** - * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable - * by double-tapping on the side. - */ -@Composable -private fun SplitLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - modifier: Modifier = Modifier, -) { - SwappableLayout( - startContent = { startContentModifier -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.SPLIT, - outputOnly = true, - modifier = startContentModifier, - ) - }, - endContent = { endContentModifier -> - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.INPUT_ONLY, - layout = BouncerSceneLayout.SPLIT, - modifier = endContentModifier, - ) - }, - layout = BouncerSceneLayout.SPLIT, - modifier = modifier, - ) -} - -/** - * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background - * to flip their positions. - */ -@Composable -private fun SwappableLayout( - startContent: @Composable (Modifier) -> Unit, - endContent: @Composable (Modifier) -> Unit, - layout: BouncerSceneLayout, - modifier: Modifier = Modifier, -) { - val layoutDirection = LocalLayoutDirection.current - val isLeftToRight = layoutDirection == LayoutDirection.Ltr - val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } - - Row( - modifier = - modifier.pointerInput(Unit) { - detectTapGestures( - onDoubleTap = { offset -> - // Depending on where the user double tapped, switch the elements such that - // the endContent is closer to the side that was double tapped. - setSwapped(offset.x < size.width / 2) - } - ) - }, - ) { - val animatedOffset by - animateFloatAsState( - targetValue = - if (!isSwapped) { - // When startContent is first, both elements have their natural placement so - // they are not offset in any way. - 0f - } else if (isLeftToRight) { - // Since startContent is not first, the elements have to be swapped - // horizontally. In the case of LTR locales, this means pushing startContent - // to the right, hence the positive number. - 1f - } else { - // Since startContent is not first, the elements have to be swapped - // horizontally. In the case of RTL locales, this means pushing startContent - // to the left, hence the negative number. - -1f - }, - label = "offset", - ) - - startContent( - Modifier.fillMaxHeight().weight(1f).graphicsLayer { - translationX = size.width * animatedOffset - alpha = animatedAlpha(animatedOffset) - } - ) - - Box( - modifier = - Modifier.fillMaxHeight().weight(1f).graphicsLayer { - // A negative sign is used to make sure this is offset in the direction that's - // opposite of the direction that the user switcher is pushed in. - translationX = -size.width * animatedOffset - alpha = animatedAlpha(animatedOffset) - } - ) { - endContent(Modifier.align(layout.swappableEndContentAlignment).widthIn(max = 400.dp)) - } - } -} - -/** - * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap - * anywhere on the background to flip their positions. - * - * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the - * UI for the bouncer will be shown on its own, taking up one side, with the other side just being - * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard - * rendering of the bouncer will be used instead of the side-by-side layout. - */ -@Composable -private fun SideBySideLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - isUserSwitcherVisible: Boolean, - modifier: Modifier = Modifier, -) { - SwappableLayout( - startContent = { startContentModifier -> - if (isUserSwitcherVisible) { - UserSwitcher( - viewModel = viewModel, - modifier = startContentModifier, - ) - } else { - Box( - modifier = startContentModifier, - ) - } - }, - endContent = { endContentModifier -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.SIDE_BY_SIDE, - modifier = endContentModifier, - ) - }, - layout = BouncerSceneLayout.SIDE_BY_SIDE, - modifier = modifier, - ) -} - -/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ -@Composable -private fun StackedLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - isUserSwitcherVisible: Boolean, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier, - ) { - if (isUserSwitcherVisible) { - UserSwitcher( - viewModel = viewModel, - modifier = Modifier.fillMaxWidth().weight(1f), - ) - } - - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.STACKED, - modifier = Modifier.fillMaxWidth().weight(1f), - ) - } -} - interface BouncerDialogFactory { operator fun invoke(): AlertDialog } -/** Enumerates all supported user-input area visibilities. */ -private enum class UserInputAreaVisibility { - /** - * Only the area where the user enters the input is shown; the area where the input is reflected - * back to the user is not shown. - */ - INPUT_ONLY, - /** - * Only the area where the input is reflected back to the user is shown; the area where the - * input is entered by the user is not shown. - */ - OUTPUT_ONLY, -} - /** * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of * the two reaches a stopping point but `0` in the middle of the transition. @@ -774,48 +877,3 @@ private object SceneElements { private val SceneTransitions = transitions { from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() } } - -/** Whether a more compact size should be used for various spacing dimensions. */ -internal val BouncerSceneLayout.isUseCompactSize: Boolean - get() = - when (this) { - BouncerSceneLayout.SIDE_BY_SIDE -> true - BouncerSceneLayout.SPLIT -> true - else -> false - } - -/** Amount of space to place between the message and the entered input UI elements, in dips. */ -private val BouncerSceneLayout.spacingBetweenMessageAndEnteredInput: Dp - get() = - when { - this == BouncerSceneLayout.STACKED -> 24.dp - isUseCompactSize -> 96.dp - else -> 128.dp - } - -/** Amount of space to place above the topmost UI element, in dips. */ -private val BouncerSceneLayout.topPadding: Dp - get() = - if (this == BouncerSceneLayout.SPLIT) { - 40.dp - } else { - 92.dp - } - -/** Amount of space to place below the bottommost UI element, in dips. */ -private val BouncerSceneLayout.bottomPadding: Dp - get() = - if (this == BouncerSceneLayout.SPLIT) { - 40.dp - } else { - 48.dp - } - -/** The in-a-box alignment for the content on the "end" side of a swappable layout. */ -private val BouncerSceneLayout.swappableEndContentAlignment: Alignment - get() = - if (this == BouncerSceneLayout.SPLIT) { - Alignment.Center - } else { - Alignment.BottomCenter - } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt index 08b7559dcf97..1c3d93c3b7e9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt @@ -26,8 +26,8 @@ import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal /** * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If - * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by - * [BouncerSceneLayout.STANDARD]. + * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced by + * [BouncerSceneLayout.STANDARD_BOUNCER]. */ @Composable fun calculateLayout( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 279995959da2..09608115c456 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -17,7 +17,6 @@ package com.android.systemui.bouncer.ui.composable import android.view.ViewTreeObserver -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.LocalTextStyle @@ -31,7 +30,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.FocusRequester @@ -81,42 +79,38 @@ internal fun PasswordBouncer( } } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier, - ) { - val color = MaterialTheme.colorScheme.onSurfaceVariant - val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() } + val color = MaterialTheme.colorScheme.onSurfaceVariant + val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() } - TextField( - value = password, - onValueChange = viewModel::onPasswordInputChanged, - enabled = isInputEnabled, - visualTransformation = PasswordVisualTransformation(), - singleLine = true, - textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - keyboardOptions = - KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - keyboardActions = - KeyboardActions( - onDone = { viewModel.onAuthenticateKeyPressed() }, - ), - modifier = - Modifier.focusRequester(focusRequester) - .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } - .drawBehind { - drawLine( - color = color, - start = Offset(x = 0f, y = size.height - lineWidthPx), - end = Offset(size.width, y = size.height - lineWidthPx), - strokeWidth = lineWidthPx, - ) - }, - ) - } + TextField( + value = password, + onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, + visualTransformation = PasswordVisualTransformation(), + singleLine = true, + textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), + keyboardOptions = + KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + keyboardActions = + KeyboardActions( + onDone = { viewModel.onAuthenticateKeyPressed() }, + ), + modifier = + modifier + .focusRequester(focusRequester) + .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } + .drawBehind { + drawLine( + color = color, + start = Offset(x = 0f, y = size.height - lineWidthPx), + end = Offset(size.width, y = size.height - lineWidthPx), + strokeWidth = lineWidthPx, + ) + }, + ) } /** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */ diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index a4b195508b3d..0a5f5d281f83 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -24,6 +24,8 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -48,7 +50,6 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.modifiers.thenIf import com.android.internal.R -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel import kotlin.math.min @@ -61,11 +62,14 @@ import kotlinx.coroutines.launch * UI for the input part of a pattern-requiring version of the bouncer. * * The user can press, hold, and drag their pointer to select dots along a grid of dots. + * + * If [centerDotsVertically] is `true`, the dots should be centered along the axis of interest; if + * `false`, the dots will be pushed towards the end/bottom of the axis. */ @Composable internal fun PatternBouncer( viewModel: PatternBouncerViewModel, - layout: BouncerSceneLayout, + centerDotsVertically: Boolean, modifier: Modifier = Modifier, ) { DisposableEffect(Unit) { @@ -197,6 +201,14 @@ internal fun PatternBouncer( Canvas( modifier + // Because the width also includes spacing to the left and right of the leftmost and + // rightmost dots in the grid and because UX mocks specify the width without that + // spacing, the actual width needs to be defined slightly bigger than the UX mock width. + .width((262 * colCount / 2).dp) + // Because the height also includes spacing above and below the topmost and bottommost + // dots in the grid and because UX mocks specify the height without that spacing, the + // actual height needs to be defined slightly bigger than the UX mock height. + .height((262 * rowCount / 2).dp) // Need to clip to bounds to make sure that the lines don't follow the input pointer // when it leaves the bounds of the dot grid. .clipToBounds() @@ -260,7 +272,7 @@ internal fun PatternBouncer( availableSize = containerSize.height, spacingPerDot = spacing, dotCount = rowCount, - isCentered = layout.isCenteredVertically, + isCentered = centerDotsVertically, ) offset = Offset(horizontalOffset, verticalOffset) scale = (colCount * spacing) / containerSize.width @@ -423,10 +435,6 @@ private fun offset( } } -/** Whether the UI should be centered vertically. */ -private val BouncerSceneLayout.isCenteredVertically: Boolean - get() = this == BouncerSceneLayout.SPLIT - private const val DOT_DIAMETER_DP = 16 private const val SELECTED_DOT_DIAMETER_DP = 24 private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83 diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 8f5d9f4a1790..f505b9067140 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -52,7 +52,6 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid import com.android.compose.modifiers.thenIf -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.ContentDescription @@ -70,7 +69,7 @@ import kotlinx.coroutines.launch @Composable fun PinPad( viewModel: PinBouncerViewModel, - layout: BouncerSceneLayout, + verticalSpacing: Dp, modifier: Modifier = Modifier, ) { DisposableEffect(Unit) { @@ -96,8 +95,8 @@ fun PinPad( VerticalGrid( columns = columns, - verticalSpacing = layout.verticalSpacing, - horizontalSpacing = calculateHorizontalSpacingBetweenColumns(layout.gridWidth), + verticalSpacing = verticalSpacing, + horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp), modifier = modifier, ) { repeat(9) { index -> @@ -355,14 +354,6 @@ private fun calculateHorizontalSpacingBetweenColumns( return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1) } -/** The width of the grid of PIN pad buttons, in dips. */ -private val BouncerSceneLayout.gridWidth: Dp - get() = if (isUseCompactSize) 292.dp else 300.dp - -/** The spacing between rows of PIN pad buttons, in dips. */ -private val BouncerSceneLayout.verticalSpacing: Dp - get() = if (isUseCompactSize) 8.dp else 12.dp - /** Number of columns in the PIN pad grid. */ private const val columns = 3 /** Maximum size (width and height) of each PIN pad button. */ 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/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index e8ecd3a66186..2a9cf0fdc507 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -25,10 +25,12 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan @@ -36,23 +38,41 @@ import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel @@ -66,21 +86,38 @@ fun CommunalHub( modifier: Modifier = Modifier, viewModel: BaseCommunalViewModel, onOpenWidgetPicker: (() -> Unit)? = null, + onEditDone: (() -> Unit)? = null, ) { val communalContent by viewModel.communalContent.collectAsState(initial = emptyList()) + var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } + var toolbarSize: IntSize? by remember { mutableStateOf(null) } + var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } + var isDraggingToRemove by remember { mutableStateOf(false) } + Box( modifier = modifier.fillMaxSize().background(Color.White), ) { CommunalHubLazyGrid( - modifier = Modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), + modifier = Modifier.align(Alignment.CenterStart), communalContent = communalContent, - isEditMode = viewModel.isEditMode, viewModel = viewModel, - ) - if (viewModel.isEditMode && onOpenWidgetPicker != null) { - IconButton(onClick = onOpenWidgetPicker) { - Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) + contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize), + setGridCoordinates = { gridCoordinates = it }, + updateDragPositionForRemove = { + isDraggingToRemove = + checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates) + isDraggingToRemove } + ) + + if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) { + Toolbar( + isDraggingToRemove = isDraggingToRemove, + setToolbarSize = { toolbarSize = it }, + setRemoveButtonCoordinates = { removeButtonCoordinates = it }, + onEditDone = onEditDone, + onOpenWidgetPicker = onOpenWidgetPicker, + ) } else { IconButton(onClick = viewModel::onOpenWidgetEditor) { Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor)) @@ -103,25 +140,38 @@ fun CommunalHub( @Composable private fun CommunalHubLazyGrid( communalContent: List<CommunalContentModel>, - isEditMode: Boolean, viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier, + contentPadding: PaddingValues, + setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit, + updateDragPositionForRemove: (offset: Offset) -> Boolean, ) { var gridModifier = modifier val gridState = rememberLazyGridState() var list = communalContent var dragDropState: GridDragDropState? = null - if (isEditMode && viewModel is CommunalEditModeViewModel) { + if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) { val contentListState = rememberContentListState(communalContent, viewModel) list = contentListState.list - dragDropState = rememberGridDragDropState(gridState, contentListState) - gridModifier = gridModifier.dragContainer(dragDropState) + dragDropState = + rememberGridDragDropState( + gridState = gridState, + contentListState = contentListState, + updateDragPositionForRemove = updateDragPositionForRemove + ) + gridModifier = + gridModifier + .fillMaxSize() + .dragContainer(dragDropState, beforeContentPadding(contentPadding)) + .onGloballyPositioned { setGridCoordinates(it) } + } else { + gridModifier = gridModifier.height(Dimensions.GridHeight) } LazyHorizontalGrid( modifier = gridModifier, state = gridState, rows = GridCells.Fixed(CommunalContentSize.FULL.span), - contentPadding = PaddingValues(horizontal = Dimensions.Spacing), + contentPadding = contentPadding, horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing), verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing), ) { @@ -130,19 +180,18 @@ private fun CommunalHubLazyGrid( key = { index -> list[index].key }, span = { index -> GridItemSpan(list[index].size.span) }, ) { index -> - val cardModifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth) + val cardModifier = Modifier.width(Dimensions.CardWidth) val size = SizeF( Dimensions.CardWidth.value, list[index].size.dp().value, ) - if (isEditMode && dragDropState != null) { + if (viewModel.isEditMode && dragDropState != null) { DraggableItem(dragDropState = dragDropState, enabled = true, index = index) { isDragging -> val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp) CommunalContent( modifier = cardModifier, - deleteOnClick = viewModel::onDeleteWidget, elevation = elevation, model = list[index], viewModel = viewModel, @@ -161,6 +210,95 @@ private fun CommunalHubLazyGrid( } } +/** + * Toolbar that contains action buttons to + * 1) open the widget picker + * 2) remove a widget from the grid and + * 3) exit the edit mode. + */ +@Composable +private fun Toolbar( + isDraggingToRemove: Boolean, + setToolbarSize: (toolbarSize: IntSize) -> Unit, + setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit, + onOpenWidgetPicker: () -> Unit, + onEditDone: () -> Unit, +) { + Row( + modifier = + Modifier.fillMaxWidth() + .padding( + top = Dimensions.ToolbarPaddingTop, + start = Dimensions.ToolbarPaddingHorizontal, + end = Dimensions.ToolbarPaddingHorizontal, + ) + .onSizeChanged { setToolbarSize(it) }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + val buttonContentPadding = + PaddingValues( + vertical = Dimensions.ToolbarButtonPaddingVertical, + horizontal = Dimensions.ToolbarButtonPaddingHorizontal, + ) + val spacerModifier = Modifier.width(Dimensions.ToolbarButtonSpaceBetween) + Button( + onClick = onOpenWidgetPicker, + colors = filledSecondaryButtonColors(), + contentPadding = buttonContentPadding + ) { + Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor)) + Spacer(spacerModifier) + Text( + text = stringResource(R.string.hub_mode_add_widget_button_text), + ) + } + + val buttonColors = + if (isDraggingToRemove) filledButtonColors() else ButtonDefaults.outlinedButtonColors() + OutlinedButton( + onClick = {}, + colors = buttonColors, + contentPadding = buttonContentPadding, + modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }, + ) { + Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor)) + Spacer(spacerModifier) + Text( + text = stringResource(R.string.button_to_remove_widget), + ) + } + + Button( + onClick = onEditDone, + colors = filledButtonColors(), + contentPadding = buttonContentPadding + ) { + Text( + text = stringResource(R.string.hub_mode_editing_exit_button_text), + ) + } + } +} + +@Composable +private fun filledButtonColors(): ButtonColors { + val colors = LocalAndroidColorScheme.current + return ButtonDefaults.buttonColors( + containerColor = colors.primary, + contentColor = colors.onPrimary, + ) +} + +@Composable +private fun filledSecondaryButtonColors(): ButtonColors { + val colors = LocalAndroidColorScheme.current + return ButtonDefaults.buttonColors( + containerColor = colors.secondary, + contentColor = colors.onSecondary, + ) +} + @Composable private fun CommunalContent( model: CommunalContentModel, @@ -168,11 +306,9 @@ private fun CommunalContent( size: SizeF, modifier: Modifier = Modifier, elevation: Dp = 0.dp, - deleteOnClick: ((id: Int) -> Unit)? = null, ) { when (model) { - is CommunalContentModel.Widget -> - WidgetContent(model, size, elevation, deleteOnClick, modifier) + is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) is CommunalContentModel.Umo -> Umo(viewModel, modifier) @@ -184,19 +320,12 @@ private fun WidgetContent( model: CommunalContentModel.Widget, size: SizeF, elevation: Dp, - deleteOnClick: ((id: Int) -> Unit)?, modifier: Modifier = Modifier, ) { - // TODO(b/309009246): update background color Card( - modifier = modifier.fillMaxSize().background(Color.White), + modifier = modifier.height(size.height.dp), elevation = CardDefaults.cardElevation(draggedElevation = elevation), ) { - if (deleteOnClick != null) { - IconButton(onClick = { deleteOnClick(model.appWidgetId) }) { - Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget)) - } - } AndroidView( modifier = modifier, factory = { context -> @@ -249,6 +378,60 @@ private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) ) } +/** + * Returns the `contentPadding` of the grid. Use the vertical padding to push the grid content area + * below the toolbar and let the grid take the max size. This ensures the item can be dragged + * outside the grid over the toolbar, without part of it getting clipped by the container. + */ +@Composable +private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues { + if (!isEditMode || toolbarSize == null) { + return PaddingValues(horizontal = Dimensions.Spacing) + } + val configuration = LocalConfiguration.current + val density = LocalDensity.current + val screenHeight = configuration.screenHeightDp.dp + val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() } + val verticalPadding = + ((screenHeight - toolbarHeight - Dimensions.GridHeight) / 2).coerceAtLeast( + Dimensions.Spacing + ) + return PaddingValues( + start = Dimensions.ToolbarPaddingHorizontal, + end = Dimensions.ToolbarPaddingHorizontal, + top = verticalPadding + toolbarHeight, + bottom = verticalPadding + ) +} + +@Composable +private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx { + return with(LocalDensity.current) { + ContentPaddingInPx( + startPadding = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(), + topPadding = paddingValues.calculateTopPadding().toPx() + ) + } +} + +/** + * Check whether the pointer position that the item is being dragged at is within the coordinates of + * the remove button in the toolbar. Returns true if the item is removable. + */ +private fun checkForDraggingToRemove( + offset: Offset, + removeButtonCoordinates: LayoutCoordinates?, + gridCoordinates: LayoutCoordinates?, +): Boolean { + if (removeButtonCoordinates == null || gridCoordinates == null) { + return false + } + val pointer = gridCoordinates.positionInWindow() + offset + val removeButton = removeButtonCoordinates.positionInWindow() + return pointer.x in removeButton.x..removeButton.x + removeButtonCoordinates.size.width && + pointer.y in removeButton.y..removeButton.y + removeButtonCoordinates.size.height +} + private fun CommunalContentSize.dp(): Dp { return when (this) { CommunalContentSize.FULL -> Dimensions.CardHeightFull @@ -257,6 +440,8 @@ private fun CommunalContentSize.dp(): Dp { } } +data class ContentPaddingInPx(val startPadding: Float, val topPadding: Float) + object Dimensions { val CardWidth = 464.dp val CardHeightFull = 630.dp @@ -264,4 +449,11 @@ object Dimensions { val CardHeightThird = 199.dp val GridHeight = CardHeightFull val Spacing = 16.dp + + // The sizing/padding of the toolbar in glanceable hub edit mode + val ToolbarPaddingTop = 27.dp + val ToolbarPaddingHorizontal = 16.dp + val ToolbarButtonPaddingHorizontal = 24.dp + val ToolbarButtonPaddingVertical = 16.dp + val ToolbarButtonSpaceBetween = 8.dp } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index 6cfa2f233f46..5451d0550095 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -48,12 +48,18 @@ import kotlinx.coroutines.launch @Composable fun rememberGridDragDropState( gridState: LazyGridState, - contentListState: ContentListState + contentListState: ContentListState, + updateDragPositionForRemove: (offset: Offset) -> Boolean, ): GridDragDropState { val scope = rememberCoroutineScope() val state = remember(gridState, contentListState) { - GridDragDropState(state = gridState, contentListState = contentListState, scope = scope) + GridDragDropState( + state = gridState, + contentListState = contentListState, + scope = scope, + updateDragPositionForRemove = updateDragPositionForRemove + ) } LaunchedEffect(state) { while (true) { @@ -67,23 +73,30 @@ fun rememberGridDragDropState( /** * Handles drag and drop cards in the glanceable hub. While dragging to move, other items that are * affected will dynamically get positioned and the state is tracked by [ContentListState]. When - * dragging to remove, affected cards will be moved and [ContentListState.onRemove] is called to - * remove the dragged item. On dragging ends, call [ContentListState.onSaveList] to persist the - * change. + * dragging to remove, affected cards will be moved and [updateDragPositionForRemove] is called to + * check whether the dragged item can be removed. On dragging ends, call [ContentListState.onRemove] + * to remove the dragged item if condition met and call [ContentListState.onSaveList] to persist any + * change in ordering. */ class GridDragDropState internal constructor( private val state: LazyGridState, private val contentListState: ContentListState, private val scope: CoroutineScope, + private val updateDragPositionForRemove: (offset: Offset) -> Boolean ) { var draggingItemIndex by mutableStateOf<Int?>(null) private set + var isDraggingToRemove by mutableStateOf(false) + private set + internal val scrollChannel = Channel<Float>() private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero) private var draggingItemInitialOffset by mutableStateOf(Offset.Zero) + private var dragStartPointerOffset by mutableStateOf(Offset.Zero) + internal val draggingItemOffset: Offset get() = draggingItemLayoutInfo?.let { item -> @@ -94,27 +107,36 @@ internal constructor( private val draggingItemLayoutInfo: LazyGridItemInfo? get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex } - internal fun onDragStart(offset: Offset) { + internal fun onDragStart(offset: Offset, contentOffset: Offset) { state.layoutInfo.visibleItemsInfo .firstOrNull { item -> + // grid item offset is based off grid content container so we need to deduct + // before content padding from the initial pointer position item.isEditable && - offset.x.toInt() in item.offset.x..item.offsetEnd.x && - offset.y.toInt() in item.offset.y..item.offsetEnd.y + (offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x && + (offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y } ?.apply { + dragStartPointerOffset = offset - this.offset.toOffset() draggingItemIndex = index draggingItemInitialOffset = this.offset.toOffset() } } internal fun onDragInterrupted() { - if (draggingItemIndex != null) { + draggingItemIndex?.let { + if (isDraggingToRemove) { + contentListState.onRemove(it) + isDraggingToRemove = false + updateDragPositionForRemove(Offset.Zero) + } // persist list editing changes on dragging ends contentListState.onSaveList() draggingItemIndex = null } draggingItemDraggedDelta = Offset.Zero draggingItemInitialOffset = Offset.Zero + dragStartPointerOffset = Offset.Zero } internal fun onDrag(offset: Offset) { @@ -152,18 +174,13 @@ internal constructor( contentListState.onMove(draggingItem.index, targetItem.index) } draggingItemIndex = targetItem.index + isDraggingToRemove = false } else { val overscroll = checkForOverscroll(startOffset, endOffset) if (overscroll != 0f) { scrollChannel.trySend(overscroll) } - val removeOffset = checkForRemove(startOffset) - if (removeOffset != 0f) { - draggingItemIndex?.let { - contentListState.onRemove(it) - draggingItemIndex = null - } - } + isDraggingToRemove = checkForRemove(startOffset) } } @@ -185,14 +202,11 @@ internal constructor( } } - // TODO(b/309968801): a temporary solution to decide whether to remove card when it's dragged up - // and out of grid. Once we have a taskbar, calculate the intersection of the dragged item with - // the Remove button. - private fun checkForRemove(startOffset: Offset): Float { + /** Calls the callback with the updated drag position and returns whether to remove the item. */ + private fun checkForRemove(startOffset: Offset): Boolean { return if (draggingItemDraggedDelta.y < 0) - (startOffset.y + Dimensions.CardHeightHalf.value - state.layoutInfo.viewportStartOffset) - .coerceAtMost(0f) - else 0f + updateDragPositionForRemove(startOffset + dragStartPointerOffset) + else false } } @@ -204,14 +218,22 @@ private operator fun Offset.plus(size: Size): Offset { return Offset(x + size.width, y + size.height) } -fun Modifier.dragContainer(dragDropState: GridDragDropState): Modifier { - return pointerInput(dragDropState) { +fun Modifier.dragContainer( + dragDropState: GridDragDropState, + beforeContentPadding: ContentPaddingInPx +): Modifier { + return pointerInput(dragDropState, beforeContentPadding) { detectDragGesturesAfterLongPress( onDrag = { change, offset -> change.consume() dragDropState.onDrag(offset = offset) }, - onDragStart = { offset -> dragDropState.onDragStart(offset) }, + onDragStart = { offset -> + dragDropState.onDragStart( + offset, + Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding) + ) + }, onDragEnd = { dragDropState.onDragInterrupted() }, onDragCancel = { dragDropState.onDragInterrupted() } ) @@ -237,6 +259,7 @@ fun LazyGridItemScope.DraggableItem( Modifier.zIndex(1f).graphicsLayer { translationX = dragDropState.draggingItemOffset.x translationY = dragDropState.draggingItemOffset.y + alpha = if (dragDropState.isDraggingToRemove) 0.5f else 1f } } else { Modifier.animateItemPlacement() diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index c49c19785624..12f1b301c836 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -27,11 +27,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onPlaced @@ -89,13 +91,18 @@ fun SceneScope.NotificationStack( isScrimVisible: Boolean, modifier: Modifier = Modifier, ) { + val cornerRadius by viewModel.cornerRadiusDp.collectAsState() + Box(modifier = modifier) { if (isScrimVisible) { Box( modifier = Modifier.element(Notifications.Elements.NotificationScrim) .fillMaxSize() - .clip(RoundedCornerShape(32.dp)) + .graphicsLayer { + shape = RoundedCornerShape(cornerRadius.dp) + clip = true + } .background(MaterialTheme.colorScheme.surface) ) } @@ -167,7 +174,9 @@ private fun SceneScope.NotificationPlaceholder( } val boundsInWindow = coordinates.boundsInWindow() viewModel.onBoundsChanged( + left = boundsInWindow.left, top = boundsInWindow.top, + right = boundsInWindow.right, bottom = boundsInWindow.bottom, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt index 009f8bb38e61..e538e093e60f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt @@ -175,11 +175,7 @@ private fun <T> computeValue( canOverflow: Boolean, ): T { val state = layoutImpl.state.transitionState - if ( - state !is TransitionState.Transition || - state.fromScene == state.toScene || - !layoutImpl.isTransitionReady(state) - ) { + if (state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state)) { return sharedValue.value } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 199832bc4ab6..de69c37d4630 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -47,12 +47,6 @@ internal fun CoroutineScope.animateToScene( when (state) { is TransitionState.Idle -> animate(layoutImpl, target) is TransitionState.Transition -> { - if (state.toScene == state.fromScene) { - // Same as idle. - animate(layoutImpl, target) - return - } - // A transition is currently running: first check whether `transition.toScene` or // `transition.fromScene` is the same as our target scene, in which case the transition // can be accelerated or reversed to end up in the target state. @@ -153,13 +147,13 @@ private fun CoroutineScope.animate( } private class OneOffTransition( - override val fromScene: SceneKey, - override val toScene: SceneKey, + fromScene: SceneKey, + toScene: SceneKey, override val currentScene: SceneKey, override val isInitiatedByUserInput: Boolean, override val isUserInputOngoing: Boolean, private val animatable: Animatable<Float, AnimationVector1D>, -) : TransitionState.Transition { +) : TransitionState.Transition(fromScene, toScene) { override val progress: Float get() = animatable.value } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 2b1195229c76..431a8aef6d3d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -213,22 +213,11 @@ internal class ElementNode( override fun onDetach() { super.onDetach() removeNodeFromSceneValues() + maybePruneMaps(layoutImpl, element, sceneValues) } private fun removeNodeFromSceneValues() { sceneValues.nodes.remove(this) - - // If element is not composed from this scene anymore, remove the scene values. This works - // because [onAttach] is called before [onDetach], so if an element is moved from the UI - // tree we will first add the new code location then remove the old one. - if (sceneValues.nodes.isEmpty()) { - element.sceneValues.remove(sceneValues.scene) - } - - // If the element is not composed in any scene, remove it from the elements map. - if (element.sceneValues.isEmpty()) { - layoutImpl.elements.remove(element.key) - } } fun update( @@ -237,12 +226,16 @@ internal class ElementNode( element: Element, sceneValues: Element.TargetValues, ) { + check(layoutImpl == this.layoutImpl && scene == this.scene) removeNodeFromSceneValues() - this.layoutImpl = layoutImpl - this.scene = scene + + val prevElement = this.element + val prevSceneValues = this.sceneValues this.element = element this.sceneValues = sceneValues + addNodeToSceneValues() + maybePruneMaps(layoutImpl, prevElement, prevSceneValues) } override fun ContentDrawScope.draw() { @@ -261,6 +254,28 @@ internal class ElementNode( } } } + + companion object { + private fun maybePruneMaps( + layoutImpl: SceneTransitionLayoutImpl, + element: Element, + sceneValues: Element.TargetValues, + ) { + // If element is not composed from this scene anymore, remove the scene values. This + // works because [onAttach] is called before [onDetach], so if an element is moved from + // the UI tree we will first add the new code location then remove the old one. + if ( + sceneValues.nodes.isEmpty() && element.sceneValues[sceneValues.scene] == sceneValues + ) { + element.sceneValues.remove(sceneValues.scene) + + // If the element is not composed in any scene, remove it from the elements map. + if (element.sceneValues.isEmpty() && layoutImpl.elements[element.key] == element) { + layoutImpl.elements.remove(element.key) + } + } + } + } } private fun shouldDrawElement( @@ -273,7 +288,6 @@ private fun shouldDrawElement( // Always draw the element if there is no ongoing transition or if the element is not shared. if ( state !is TransitionState.Transition || - state.fromScene == state.toScene || !layoutImpl.isTransitionReady(state) || state.fromScene !in element.sceneValues || state.toScene !in element.sceneValues @@ -359,7 +373,7 @@ private fun isElementOpaque( ): Boolean { val state = layoutImpl.state.transitionState - if (state !is TransitionState.Transition || state.fromScene == state.toScene) { + if (state !is TransitionState.Transition) { return true } @@ -596,7 +610,7 @@ private inline fun <T> computeValue( val state = layoutImpl.state.transitionState // There is no ongoing transition. - if (state !is TransitionState.Transition || state.fromScene == state.toScene) { + if (state !is TransitionState.Transition) { // Even if this element SceneTransitionLayout is not animated, the layout itself might be // animated (e.g. by another parent SceneTransitionLayout), in which case this element still // need to participate in the layout phase. @@ -615,7 +629,9 @@ private inline fun <T> computeValue( val toValues = element.sceneValues[toScene] if (fromValues == null && toValues == null) { - error("This should not happen, element $element is neither in $fromScene or $toScene") + // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not + // run anymore. + return lastValue() } // The element is shared: interpolate between the value in fromScene and the value in toScene. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index fa385d014ccb..7029da2edb0d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -131,10 +131,6 @@ private fun shouldComposeMovableElement( val fromScene = (transitionState as TransitionState.Transition).fromScene val toScene = transitionState.toScene - if (fromScene == toScene) { - check(fromScene == scene) - return true - } val fromReady = layoutImpl.isSceneReady(fromScene) val toReady = layoutImpl.isSceneReady(toScene) 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/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt index 1b79dbdee510..983cff83ed57 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt @@ -73,17 +73,13 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans when (val state = transitionState) { is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene) is TransitionState.Transition -> { - if (state.fromScene == state.toScene) { - ObservableTransitionState.Idle(state.currentScene) - } else { - ObservableTransitionState.Transition( - fromScene = state.fromScene, - toScene = state.toScene, - progress = snapshotFlow { state.progress }, - isInitiatedByUserInput = state.isInitiatedByUserInput, - isUserInputOngoing = snapshotFlow { state.isUserInputOngoing }, - ) - } + ObservableTransitionState.Transition( + fromScene = state.fromScene, + toScene = state.toScene, + progress = snapshotFlow { state.progress }, + isInitiatedByUserInput = state.isInitiatedByUserInput, + isUserInputOngoing = snapshotFlow { state.isUserInputOngoing }, + ) } } } 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 212c9eb65138..91decf4d8b7e 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 @@ -49,8 +49,12 @@ internal class SceneGestureHandler( layoutImpl.state.transitionState = value } - internal var swipeTransition: SwipeTransition = SwipeTransition(currentScene, currentScene, 1f) - private set + private var _swipeTransition: SwipeTransition? = null + internal var swipeTransition: SwipeTransition + get() = _swipeTransition ?: error("SwipeTransition needs to be initialized") + set(value) { + _swipeTransition = value + } private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) { if (isDrivingTransition || force) transitionState = newTransition @@ -61,7 +65,7 @@ internal class SceneGestureHandler( get() = layoutImpl.scene(transitionState.currentScene) internal val isDrivingTransition - get() = transitionState == swipeTransition + get() = transitionState == _swipeTransition /** * The velocity threshold at which the intent of the user is to swipe up or down. It is the same @@ -82,12 +86,15 @@ internal class SceneGestureHandler( private var actionDownOrRight: UserAction? = null private var actionUpOrLeftNoEdge: UserAction? = null private var actionDownOrRightNoEdge: UserAction? = null + private var upOrLeftScene: SceneKey? = null + private var downOrRightScene: SceneKey? = null internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) { if (isDrivingTransition) { // This [transition] was already driving the animation: simply take over it. // Stop animating and start from where the current offset. swipeTransition.cancelOffsetAnimation() + updateTargetScenes(swipeTransition._fromScene) return } @@ -105,11 +112,8 @@ internal class SceneGestureHandler( val fromScene = currentScene setCurrentActions(fromScene, startedPosition, pointersDown) - if (fromScene.upOrLeft() == null && fromScene.downOrRight() == null) { - return - } - - val (targetScene, distance) = fromScene.findTargetSceneAndDistance(overSlop) + val (targetScene, distance) = + findTargetSceneAndDistance(fromScene, overSlop, updateScenes = true) ?: return updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true) } @@ -179,16 +183,21 @@ internal class SceneGestureHandler( val (fromScene, acceleratedOffset) = computeFromSceneConsideringAcceleratedSwipe(swipeTransition) - swipeTransition.dragOffset += acceleratedOffset - // Compute the target scene depending on the current offset. + val isNewFromScene = fromScene.key != swipeTransition.fromScene val (targetScene, distance) = - fromScene.findTargetSceneAndDistance(swipeTransition.dragOffset) + findTargetSceneAndDistance( + fromScene, + swipeTransition.dragOffset, + updateScenes = isNewFromScene, + ) + ?: run { + onDragStopped(delta, true) + return + } + swipeTransition.dragOffset += acceleratedOffset - // TODO(b/290184746): support long scroll A => B => C? especially for non fullscreen scenes - if ( - fromScene.key != swipeTransition.fromScene || targetScene.key != swipeTransition.toScene - ) { + if (isNewFromScene || targetScene.key != swipeTransition.toScene) { updateTransition( SwipeTransition(fromScene, targetScene, distance).apply { this.dragOffset = swipeTransition.dragOffset @@ -197,6 +206,11 @@ internal class SceneGestureHandler( } } + private fun updateTargetScenes(fromScene: Scene) { + upOrLeftScene = fromScene.upOrLeft() + downOrRightScene = fromScene.downOrRight() + } + /** * Change fromScene in the case where the user quickly swiped multiple times in the same * direction to accelerate the transition from A => B then B => C. @@ -214,37 +228,71 @@ internal class SceneGestureHandler( val absoluteDistance = swipeTransition.distance.absoluteValue // If the swipe was not committed, don't do anything. - if (fromScene == toScene || swipeTransition._currentScene != toScene) { + if (swipeTransition._currentScene != toScene) { return Pair(fromScene, 0f) } // If the offset is past the distance then let's change fromScene so that the user can swipe // to the next screen or go back to the previous one. val offset = swipeTransition.dragOffset - return if (offset <= -absoluteDistance && fromScene.upOrLeft() == toScene.key) { + return if (offset <= -absoluteDistance && upOrLeftScene == toScene.key) { Pair(toScene, absoluteDistance) - } else if (offset >= absoluteDistance && fromScene.downOrRight() == toScene.key) { + } else if (offset >= absoluteDistance && downOrRightScene == toScene.key) { Pair(toScene, -absoluteDistance) } else { Pair(fromScene, 0f) } } - // TODO(b/290184746): there are two bugs here: - // 1. if both upOrLeft and downOrRight become `null` during a transition this will crash - // 2. if one of them changes during a transition, the transition will jump cut to the new target - private inline fun Scene.findTargetSceneAndDistance( - directionOffset: Float - ): Pair<Scene, Float> { - val upOrLeft = upOrLeft() - val downOrRight = downOrRight() - val absoluteDistance = getAbsoluteDistance() + /** + * Returns the target scene and distance from [fromScene] in the direction [directionOffset]. + * + * @param fromScene the scene from which we look for the target + * @param directionOffset signed float that indicates the direction. Positive is down or right + * negative is up or left. + * @param updateScenes whether the target scenes should be updated to the current values held in + * the Scenes map. Usually we don't want to update them while doing a drag, because this could + * change the target scene (jump cutting) to a different scene, when some system state changed + * the targets the background. However, an update is needed any time we calculate the targets + * for a new fromScene. + * @return null when there are no targets in either direction. If one direction is null and you + * drag into the null direction this function will return the opposite direction, assuming + * that the users intention is to start the drag into the other direction eventually. If + * [directionOffset] is 0f and both direction are available, it will default to + * [upOrLeftScene]. + */ + private inline fun findTargetSceneAndDistance( + fromScene: Scene, + directionOffset: Float, + updateScenes: Boolean, + ): Pair<Scene, Float>? { + if (updateScenes) updateTargetScenes(fromScene) + val absoluteDistance = fromScene.getAbsoluteDistance() // Compute the target scene depending on the current offset. - return if ((directionOffset < 0f && upOrLeft != null) || downOrRight == null) { - Pair(layoutImpl.scene(upOrLeft!!), -absoluteDistance) - } else { - Pair(layoutImpl.scene(downOrRight), absoluteDistance) + return when { + upOrLeftScene == null && downOrRightScene == null -> null + (directionOffset < 0f && upOrLeftScene != null) || downOrRightScene == null -> + Pair(layoutImpl.scene(upOrLeftScene!!), -absoluteDistance) + else -> Pair(layoutImpl.scene(downOrRightScene!!), absoluteDistance) + } + } + + /** + * A strict version of [findTargetSceneAndDistance] that will return null when there is no Scene + * in [directionOffset] direction + */ + private inline fun findTargetSceneAndDistanceStrict( + fromScene: Scene, + directionOffset: Float, + ): Pair<Scene, Float>? { + val absoluteDistance = fromScene.getAbsoluteDistance() + return when { + directionOffset > 0f -> + upOrLeftScene?.let { Pair(layoutImpl.scene(it), -absoluteDistance) } + directionOffset < 0f -> + downOrRightScene?.let { Pair(layoutImpl.scene(it), absoluteDistance) } + else -> null } } @@ -311,20 +359,21 @@ internal class SceneGestureHandler( val startFromIdlePosition = swipeTransition.dragOffset == 0f if (startFromIdlePosition) { - // If there is a next scene, we start the overscroll animation. - val (targetScene, distance) = fromScene.findTargetSceneAndDistance(velocity) - val isValidTarget = distance != 0f && targetScene.key != fromScene.key - if (isValidTarget) { - updateTransition( - SwipeTransition(fromScene, targetScene, distance).apply { - _currentScene = swipeTransition._currentScene + // If there is a target scene, we start the overscroll animation. + val (targetScene, distance) = + findTargetSceneAndDistanceStrict(fromScene, velocity) + ?: run { + // We will not animate + transitionState = TransitionState.Idle(fromScene.key) + return } - ) - animateTo(targetScene = fromScene, targetOffset = 0f) - } else { - // We will not animate - transitionState = TransitionState.Idle(fromScene.key) - } + + updateTransition( + SwipeTransition(fromScene, targetScene, distance).apply { + _currentScene = swipeTransition._currentScene + } + ) + animateTo(targetScene = fromScene, targetOffset = 0f) } else { // We were between two scenes: animate to the initial scene. animateTo(targetScene = fromScene, targetOffset = 0f) @@ -410,15 +459,11 @@ internal class SceneGestureHandler( * above or to the left of [toScene]. */ val distance: Float - ) : TransitionState.Transition { + ) : TransitionState.Transition(_fromScene.key, _toScene.key) { var _currentScene by mutableStateOf(_fromScene) override val currentScene: SceneKey get() = _currentScene.key - override val fromScene: SceneKey = _fromScene.key - - override val toScene: SceneKey = _toScene.key - override val progress: Float get() { val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset @@ -495,8 +540,8 @@ private class SceneDraggableHandler( internal class SceneNestedScrollHandler( private val gestureHandler: SceneGestureHandler, - private val startBehavior: NestedScrollBehavior, - private val endBehavior: NestedScrollBehavior, + private val topOrLeftBehavior: NestedScrollBehavior, + private val bottomOrRightBehavior: NestedScrollBehavior, ) : NestedScrollHandler { override val connection: PriorityNestedScrollConnection = nestedScrollConnection() @@ -565,8 +610,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 +639,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 } @@ -604,6 +649,7 @@ internal class SceneNestedScrollHandler( behavior.canStartOnPostFling && hasNextScene(velocityAvailable) }, canContinueScroll = { true }, + canScrollOnFling = false, onStart = { offsetAvailable -> gestureHandler.gestureWithPriority = this gestureHandler.onDragStarted( 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/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 02ddccbc051b..00e33e24c41e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -172,7 +172,7 @@ internal class SceneTransitionLayoutImpl( val width: Int val height: Int val state = state.transitionState - if (state !is TransitionState.Transition || state.fromScene == state.toScene) { + if (state !is TransitionState.Transition) { width = placeable.width height = placeable.height } else { @@ -232,10 +232,7 @@ internal class SceneTransitionLayoutImpl( is TransitionState.Idle -> drawContent() is TransitionState.Transition -> { // Don't draw scenes that are not ready yet. - if ( - readyScenes.containsKey(key) || - state.fromScene == state.toScene - ) { + if (readyScenes.containsKey(key)) { drawContent() } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index f48e9147eef4..623725582a9d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -39,11 +39,6 @@ class SceneTransitionLayoutState(initialScene: SceneKey) { fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean { val transition = transitionState as? TransitionState.Transition ?: return false - // TODO(b/310915136): Remove this check. - if (transition.fromScene == transition.toScene) { - return false - } - return (from == null || transition.fromScene == from) && (to == null || transition.toScene == to) } @@ -71,32 +66,30 @@ sealed interface TransitionState { /** No transition/animation is currently running. */ data class Idle(override val currentScene: SceneKey) : TransitionState - /** - * There is a transition animating between two scenes. - * - * Important note: [fromScene] and [toScene] might be the same, in which case this [Transition] - * should be treated the same as [Idle]. This is designed on purpose so that a [Transition] can - * be started without knowing in advance where it is transitioning to, making the logic of - * [swipeToScene] easier to reason about. - */ - interface Transition : TransitionState { - /** The scene this transition is starting from. */ - val fromScene: SceneKey + /** There is a transition animating between two scenes. */ + abstract class Transition( + /** The scene this transition is starting from. Can't be the same as toScene */ + val fromScene: SceneKey, - /** The scene this transition is going to. */ + /** The scene this transition is going to. Can't be the same as fromScene */ val toScene: SceneKey + ) : TransitionState { + + init { + check(fromScene != toScene) + } /** * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or * when flinging quickly during a swipe gesture. */ - val progress: Float + abstract val progress: Float /** Whether the transition was triggered by user input rather than being programmatic. */ - val isInitiatedByUserInput: Boolean + abstract val isInitiatedByUserInput: Boolean /** Whether user input is currently driving the transition. */ - val isUserInputOngoing: Boolean + abstract val isUserInputOngoing: Boolean } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index f820074ec3d1..dfa2a9a18e91 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -226,12 +226,17 @@ interface PropertyTransformationBuilder { ) /** - * Scale the element(s) matching [matcher] so that it grows/shrinks to the same size as [anchor] - * . + * Scale the element(s) matching [matcher] so that it grows/shrinks to the same size as + * [anchor]. * * Note: This currently only works if [anchor] is a shared element of this transition. */ - fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) + fun anchoredSize( + matcher: ElementMatcher, + anchor: ElementKey, + anchorWidth: Boolean = true, + anchorHeight: Boolean = true, + ) } /** The edge of a [SceneTransitionLayout]. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 8c0a5a394331..8f4a36e47212 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -178,7 +178,12 @@ internal class TransitionBuilderImpl : TransitionBuilder { transformation(DrawScale(matcher, scaleX, scaleY, pivot)) } - override fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) { - transformation(AnchoredSize(matcher, anchor)) + override fun anchoredSize( + matcher: ElementMatcher, + anchor: ElementKey, + anchorWidth: Boolean, + anchorHeight: Boolean, + ) { + transformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight)) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt index 95385d51cb25..40c814e0f25c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt @@ -29,6 +29,8 @@ import com.android.compose.animation.scene.TransitionState internal class AnchoredSize( override val matcher: ElementMatcher, private val anchor: ElementKey, + private val anchorWidth: Boolean, + private val anchorHeight: Boolean, ) : PropertyTransformation<IntSize> { override fun transform( layoutImpl: SceneTransitionLayoutImpl, @@ -41,7 +43,10 @@ internal class AnchoredSize( fun anchorSizeIn(scene: SceneKey): IntSize { val size = layoutImpl.elements[anchor]?.sceneValues?.get(scene)?.targetSize return if (size != null && size != Element.SizeUnspecified) { - size + IntSize( + width = if (anchorWidth) size.width else value.width, + height = if (anchorHeight) size.height else value.height, + ) } else { value } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt index cea8d9a65b43..2c96d0e5402a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt @@ -16,9 +16,8 @@ package com.android.compose.nestedscroll -import androidx.compose.ui.geometry.Offset +import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource /** * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the @@ -34,59 +33,44 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource * * @sample com.android.compose.animation.scene.demo.Shade */ -class LargeTopAppBarNestedScrollConnection( - private val height: () -> Float, - private val onChangeHeight: (Float) -> Unit, - private val minHeight: Float, - private val maxHeight: Float, -) : NestedScrollConnection { - - constructor( - height: () -> Float, - onHeightChanged: (Float) -> Unit, - heightRange: ClosedFloatingPointRange<Float>, - ) : this( - height = height, - onChangeHeight = onHeightChanged, - minHeight = heightRange.start, - maxHeight = heightRange.endInclusive, +fun LargeTopAppBarNestedScrollConnection( + height: () -> Float, + onHeightChanged: (Float) -> Unit, + heightRange: ClosedFloatingPointRange<Float>, +): PriorityNestedScrollConnection { + val minHeight = heightRange.start + val maxHeight = heightRange.endInclusive + return PriorityNestedScrollConnection( + orientation = Orientation.Vertical, + // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will + // expand. Then, you can then scroll down the content. + canStartPreScroll = { offsetAvailable, offsetBeforeStart -> + offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight + }, + // When swiping down, the content will scroll up until it reaches the top. Then, the + // LargeTopAppBar will expand until it reaches its [maxHeight]. + canStartPostScroll = { offsetAvailable, _ -> offsetAvailable > 0 && height() < maxHeight }, + canStartPostFling = { false }, + canContinueScroll = { + val currentHeight = height() + minHeight < currentHeight && currentHeight < maxHeight + }, + canScrollOnFling = true, + onStart = { /* do nothing */}, + onScroll = { offsetAvailable -> + val currentHeight = height() + val amountConsumed = + if (offsetAvailable > 0) { + val amountLeft = maxHeight - currentHeight + offsetAvailable.coerceAtMost(amountLeft) + } else { + val amountLeft = minHeight - currentHeight + offsetAvailable.coerceAtLeast(amountLeft) + } + onHeightChanged(currentHeight + amountConsumed) + amountConsumed + }, + // Don't consume the velocity on pre/post fling + onStop = { 0f }, ) - - /** - * When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will expand. - * Then, you can then scroll down the content. - */ - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - val y = available.y - val currentHeight = height() - if (y >= 0 || currentHeight <= minHeight) { - return Offset.Zero - } - - val amountLeft = minHeight - currentHeight - val amountConsumed = y.coerceAtLeast(amountLeft) - onChangeHeight(currentHeight + amountConsumed) - return Offset(0f, amountConsumed) - } - - /** - * When swiping down, the content will scroll up until it reaches the top. Then, the - * LargeTopAppBar will expand until it reaches its [maxHeight]. - */ - override fun onPostScroll( - consumed: Offset, - available: Offset, - source: NestedScrollSource - ): Offset { - val y = available.y - val currentHeight = height() - if (y <= 0 || currentHeight >= maxHeight) { - return Offset.Zero - } - - val amountLeft = maxHeight - currentHeight - val amountConsumed = y.coerceAtMost(amountLeft) - onChangeHeight(currentHeight + amountConsumed) - return Offset(0f, amountConsumed) - } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt index c49a2b8bbe32..2841bcf4e40c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt @@ -38,6 +38,7 @@ class PriorityNestedScrollConnection( private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean, private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean, private val canContinueScroll: () -> Boolean, + private val canScrollOnFling: Boolean, private val onStart: (offsetAvailable: Offset) -> Unit, private val onScroll: (offsetAvailable: Offset) -> Offset, private val onStop: (velocityAvailable: Velocity) -> Velocity, @@ -59,7 +60,7 @@ class PriorityNestedScrollConnection( if ( isPriorityMode || - source == NestedScrollSource.Fling || + (source == NestedScrollSource.Fling && !canScrollOnFling) || !canStartPostScroll(available, offsetBeforeStart) ) { // The priority mode cannot start so we won't consume the available offset. @@ -71,7 +72,7 @@ class PriorityNestedScrollConnection( override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { if (!isPriorityMode) { - if (source != NestedScrollSource.Fling) { + if (source != NestedScrollSource.Fling || canScrollOnFling) { if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) { return onPriorityStart(available) } @@ -98,12 +99,20 @@ class PriorityNestedScrollConnection( } override suspend fun onPreFling(available: Velocity): Velocity { + if (isPriorityMode && canScrollOnFling) { + // We don't want to consume the velocity, we prefer to continue receiving scroll events. + return Velocity.Zero + } // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed // of the fling gesture. return onPriorityStop(velocity = available) } override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + if (isPriorityMode) { + return onPriorityStop(velocity = available) + } + if (!canStartPostFling(available)) { return Velocity.Zero } @@ -156,6 +165,7 @@ fun PriorityNestedScrollConnection( canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean, canStartPostFling: (velocityAvailable: Float) -> Boolean, canContinueScroll: () -> Boolean, + canScrollOnFling: Boolean, onStart: (offsetAvailable: Float) -> Unit, onScroll: (offsetAvailable: Float) -> Float, onStop: (velocityAvailable: Float) -> Float, @@ -172,6 +182,7 @@ fun PriorityNestedScrollConnection( canStartPostFling(velocityAvailable.toFloat()) }, canContinueScroll = canContinueScroll, + canScrollOnFling = canScrollOnFling, onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) }, onScroll = { offsetAvailable: Offset -> onScroll(offsetAvailable.toFloat()).toOffset() diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index ce3e1db2c3d0..439dc00d2e8e 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -17,16 +17,21 @@ package com.android.compose.animation.scene import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -36,6 +41,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Rule import org.junit.Test @@ -430,6 +438,97 @@ class ElementTest { } @Test + @OptIn(ExperimentalFoundationApi::class) + fun elementModifierNodeIsRecycledInLazyLayouts() = runTest { + val nPages = 2 + val pagerState = PagerState(currentPage = 0) { nPages } + var nullableLayoutImpl: SceneTransitionLayoutImpl? = null + + // This is how we scroll a pager inside a test, as explained in b/315457147#comment2. + lateinit var scrollScope: CoroutineScope + fun scrollToPage(page: Int) { + var animationFinished by mutableStateOf(false) + rule.runOnIdle { + scrollScope.launch { + pagerState.scrollToPage(page) + animationFinished = true + } + } + rule.waitUntil(timeoutMillis = 10_000) { animationFinished } + } + + rule.setContent { + scrollScope = rememberCoroutineScope() + + SceneTransitionLayoutForTesting( + currentScene = TestScenes.SceneA, + onChangeScene = {}, + transitions = remember { transitions {} }, + state = remember { SceneTransitionLayoutState(TestScenes.SceneA) }, + edgeDetector = DefaultEdgeDetector, + modifier = Modifier, + transitionInterceptionThreshold = 0f, + onLayoutImpl = { nullableLayoutImpl = it }, + ) { + scene(TestScenes.SceneA) { + // The pages are full-size and beyondBoundsPageCount is 0, so at rest only one + // page should be composed. + HorizontalPager( + pagerState, + beyondBoundsPageCount = 0, + ) { page -> + when (page) { + 0 -> Box(Modifier.element(TestElements.Foo).fillMaxSize()) + 1 -> Box(Modifier.fillMaxSize()) + else -> error("page $page < nPages $nPages") + } + } + } + } + } + + assertThat(nullableLayoutImpl).isNotNull() + val layoutImpl = nullableLayoutImpl!! + + // There is only Foo in the elements map. + assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo) + val element = layoutImpl.elements.getValue(TestElements.Foo) + val sceneValues = element.sceneValues + assertThat(sceneValues.keys).containsExactly(TestScenes.SceneA) + + // Get the ElementModifier node that should be reused later on when coming back to this + // page. + val nodes = sceneValues.getValue(TestScenes.SceneA).nodes + assertThat(nodes).hasSize(1) + val node = nodes.single() + + // Go to the second page. + scrollToPage(1) + rule.waitForIdle() + + assertThat(nodes).isEmpty() + assertThat(sceneValues).isEmpty() + assertThat(layoutImpl.elements).isEmpty() + + // Go back to the first page. + scrollToPage(0) + rule.waitForIdle() + + assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo) + val newElement = layoutImpl.elements.getValue(TestElements.Foo) + val newSceneValues = newElement.sceneValues + assertThat(newElement).isNotEqualTo(element) + assertThat(newSceneValues).isNotEqualTo(sceneValues) + assertThat(newSceneValues.keys).containsExactly(TestScenes.SceneA) + + // The ElementModifier node should be the same as before. + val newNodes = newSceneValues.getValue(TestScenes.SceneA).nodes + assertThat(newNodes).hasSize(1) + val newNode = newNodes.single() + assertThat(newNode).isSameInstanceAs(node) + } + + @Test fun existingElementsDontRecomposeWhenTransitionStateChanges() { var fooCompositions = 0 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..e6224df649ca 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 @@ -58,16 +58,22 @@ class SceneGestureHandlerTest { private val layoutState: SceneTransitionLayoutState = SceneTransitionLayoutState(internalCurrentScene) + val mutableUserActionsA: MutableMap<UserAction, SceneKey> = + mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC) + + val mutableUserActionsB: MutableMap<UserAction, SceneKey> = + mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA) + private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = { scene( key = SceneA, - userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC), + userActions = mutableUserActionsA, ) { Text("SceneA") } scene( key = SceneB, - userActions = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA), + userActions = mutableUserActionsB, ) { Text("SceneB") } @@ -117,8 +123,8 @@ class SceneGestureHandlerTest { fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) = SceneNestedScrollHandler( gestureHandler = sceneGestureHandler, - startBehavior = nestedScrollBehavior, - endBehavior = nestedScrollBehavior, + topOrLeftBehavior = nestedScrollBehavior, + bottomOrRightBehavior = nestedScrollBehavior, ) .connection @@ -412,6 +418,70 @@ class SceneGestureHandlerTest { } @Test + fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest { + draggable.onDragStarted() + draggable.onDelta(up(0.2f)) + + draggable.onDelta(up(0.2f)) + draggable.onDragStopped(velocity = -velocityThreshold) + assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB) + + mutableUserActionsA.remove(Swipe.Up) + mutableUserActionsA.remove(Swipe.Down) + mutableUserActionsB.remove(Swipe.Up) + mutableUserActionsB.remove(Swipe.Down) + + // start accelaratedScroll and scroll over to B -> null + draggable.onDragStarted() + draggable.onDelta(up(0.5f)) + draggable.onDelta(up(0.5f)) + + // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may + // still be called. Make sure that they don't crash or change the scene + draggable.onDelta(up(0.5f)) + draggable.onDragStopped(0f) + + advanceUntilIdle() + assertIdle(SceneB) + + // These events can still come in after the animation has settled + draggable.onDelta(up(0.5f)) + draggable.onDragStopped(0f) + assertIdle(SceneB) + } + + @Test + fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest { + draggable.onDragStarted(up(0.1f)) + assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f) + + mutableUserActionsA[Swipe.Up] = SceneC + draggable.onDelta(up(0.1f)) + // target stays B even though UserActions changed + assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f) + draggable.onDragStopped(down(0.1f)) + advanceUntilIdle() + + // now target changed to C for new drag + draggable.onDragStarted(up(0.1f)) + assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f) + } + + @Test + fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest { + draggable.onDragStarted(up(0.1f)) + assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f) + + mutableUserActionsA[Swipe.Up] = SceneC + draggable.onDelta(up(0.1f)) + draggable.onDragStopped(down(0.1f)) + + // now target changed to C for new drag that started before previous drag settled to Idle + draggable.onDragStarted(up(0.1f)) + assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f) + } + + @Test fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest { draggable.onDragStarted() assertTransition(currentScene = SceneA) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index 94c51ca50667..eeda8d46adfa 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -39,18 +39,6 @@ class SceneTransitionLayoutStateTest { } @Test - fun isTransitioningTo_fromSceneEqualToToScene() { - val state = SceneTransitionLayoutState(TestScenes.SceneA) - state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneA) - - assertThat(state.isTransitioning()).isFalse() - assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse() - assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse() - assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)) - .isFalse() - } - - @Test fun isTransitioningTo_transition() { val state = SceneTransitionLayoutState(TestScenes.SceneA) state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB) @@ -64,10 +52,8 @@ class SceneTransitionLayoutStateTest { } private fun transition(from: SceneKey, to: SceneKey): TransitionState.Transition { - return object : TransitionState.Transition { + return object : TransitionState.Transition(from, to) { override val currentScene: SceneKey = from - override val fromScene: SceneKey = from - override val toScene: SceneKey = to override val progress: Float = 0f override val isInitiatedByUserInput: Boolean = false override val isUserInputOngoing: Boolean = false diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt index 8ef6757d33bd..e555a01d42fd 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt @@ -85,4 +85,50 @@ class AnchoredSizeTest { after { onElement(TestElements.Bar).assertDoesNotExist() } } } + + @Test + fun testAnchoredWidthOnly() { + rule.testTransition( + fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) }, + toSceneContent = { + Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo)) + Box(Modifier.size(200.dp, 60.dp).element(TestElements.Bar)) + }, + transition = { + spec = tween(16 * 4, easing = LinearEasing) + anchoredSize(TestElements.Bar, TestElements.Foo, anchorHeight = false) + }, + ) { + before { onElement(TestElements.Bar).assertDoesNotExist() } + at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 60.dp) } + at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 60.dp) } + at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 60.dp) } + at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 60.dp) } + at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } + after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } + } + } + + @Test + fun testAnchoredHeightOnly() { + rule.testTransition( + fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) }, + toSceneContent = { + Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo)) + Box(Modifier.size(200.dp, 60.dp).element(TestElements.Bar)) + }, + transition = { + spec = tween(16 * 4, easing = LinearEasing) + anchoredSize(TestElements.Bar, TestElements.Foo, anchorWidth = false) + }, + ) { + before { onElement(TestElements.Bar).assertDoesNotExist() } + at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 100.dp) } + at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 90.dp) } + at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 80.dp) } + at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 70.dp) } + at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } + after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } + } + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt index 03d231a7fcc6..e2974cddf1b9 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt @@ -17,6 +17,7 @@ package com.android.compose.nestedscroll import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -36,6 +37,15 @@ class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) { heightRange = heightRange, ) + private fun NestedScrollConnection.scroll( + available: Offset, + consumedByScroll: Offset = Offset.Zero, + ) { + val consumedByPreScroll = onPreScroll(available = available, source = scrollSource) + val consumed = consumedByPreScroll + consumedByScroll + onPostScroll(consumed = consumed, available = available - consumed, source = scrollSource) + } + @Test fun onScrollUp_consumeHeightFirst() { val scrollConnection = buildScrollConnection(heightRange = 0f..2f) @@ -50,6 +60,41 @@ class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) { } @Test + fun onScrollUpAfterContentScrolled_ignoreUpEvent() { + val scrollConnection = buildScrollConnection(heightRange = 0f..2f) + height = 1f + + // scroll down consumed by a child + scrollConnection.scroll(available = Offset(0f, 1f), consumedByScroll = Offset(0f, 1f)) + + val offsetConsumed = + scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource) + + // It should ignore all onPreScroll events + assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(height).isEqualTo(1f) + } + + @Test + fun onScrollUpAfterContentReturnedToZero_consumeHeight() { + val scrollConnection = buildScrollConnection(heightRange = 0f..2f) + height = 1f + + // scroll down consumed by a child + scrollConnection.scroll(available = Offset(0f, 1f), consumedByScroll = Offset(0f, 1f)) + + // scroll up consumed by a child, the child is in its original position + scrollConnection.scroll(available = Offset(0f, -1f), consumedByScroll = Offset(0f, -1f)) + + val offsetConsumed = + scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource) + + // It should ignore all onPreScroll events + assertThat(offsetConsumed).isEqualTo(Offset(0f, -1f)) + assertThat(height).isEqualTo(0f) + } + + @Test fun onScrollUp_consumeDownToMin() { val scrollConnection = buildScrollConnection(heightRange = 0f..2f) height = 0f @@ -110,6 +155,27 @@ class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) { } @Test + fun onScrollDownAfterPostScroll_consumeHeightPreScroll() { + val scrollConnection = buildScrollConnection(heightRange = 0f..2f) + height = 1f + scrollConnection.onPostScroll( + consumed = Offset.Zero, + available = Offset(x = 0f, y = 0.5f), + source = scrollSource + ) + + val offsetConsumed = + scrollConnection.onPreScroll( + available = Offset(x = 0f, y = 0.5f), + source = scrollSource + ) + assertThat(offsetConsumed).isEqualTo(Offset(0f, 0.5f)) + + // It can increase by 1 (0.5f + 0.5f) the height + assertThat(height).isEqualTo(2f) + } + + @Test fun onScrollDown_consumeUpToMax() { val scrollConnection = buildScrollConnection(heightRange = 0f..2f) height = 2f diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt index 122774bb28fe..8a9a92ead89e 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt @@ -46,6 +46,7 @@ class PriorityNestedScrollConnectionTest { canStartPostScroll = { _, _ -> canStartPostScroll }, canStartPostFling = { canStartPostFling }, canContinueScroll = { canContinueScroll }, + canScrollOnFling = false, onStart = { isStarted = true }, onScroll = { lastScroll = it diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 543b2910bbda..695d888d94f5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -465,29 +465,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test - fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() { - // GIVEN the current security method is SimPin - whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) - whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) - .thenReturn(false) - underTest.showSecurityScreen(SecurityMode.SimPin) - - // WHEN a request is made from the SimPin screens to show the next security method - whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN) - underTest.showNextSecurityScreenOrFinish( - /* authenticated= */ true, - TARGET_USER_ID, - /* bypassSecondaryLockScreen= */ true, - SecurityMode.SimPin - ) - - // THEN the next security method of PIN is set, and the keyguard is not marked as done - verify(viewMediatorCallback, never()).keyguardDonePending(anyInt()) - verify(viewMediatorCallback, never()).keyguardDone(anyInt()) - Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN) - } - - @Test fun showNextSecurityScreenOrFinish_DeviceNotSecure() { // GIVEN the current security method is SimPin whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) @@ -578,6 +555,57 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test + fun showNextSecurityScreenOrFinish_SimPin_Password() { + // GIVEN the current security method is SimPin + whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) + .thenReturn(false) + underTest.showSecurityScreen(SecurityMode.SimPin) + + // WHEN a request is made from the SimPin screens to show the next security method + whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) + .thenReturn(SecurityMode.Password) + // WHEN security method is SWIPE + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) + whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false) + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN we will not show the password screen. + verify(viewFlipperController, never()) + .getSecurityView(eq(SecurityMode.Password), any(), any()) + } + + @Test + fun showNextSecurityScreenOrFinish_SimPin_SimPin() { + // GIVEN the current security method is SimPin + whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) + .thenReturn(false) + underTest.showSecurityScreen(SecurityMode.SimPin) + + // WHEN a request is made from the SimPin screens to show the next security method + whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) + .thenReturn(SecurityMode.SimPin) + // WHEN security method is SWIPE + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) + whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false) + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN we will not show the password screen. + verify(viewFlipperController).getSecurityView(eq(SecurityMode.SimPin), any(), any()) + } + + @Test fun onSwipeUp_forwardsItToFaceAuthInteractor() { val registeredSwipeListener = registeredSwipeListener setupGetSecurityView(SecurityMode.Password) diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 94c3bde29597..84d735430edd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -34,11 +34,14 @@ import com.android.systemui.util.mockito.any import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyInt import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @@ -63,6 +66,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> + private val updateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) @Before fun setup() { @@ -95,6 +100,9 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { mSelectedUserInteractor ) underTest.init() + underTest.onResume(0) + verify(keyguardUpdateMonitor) + .registerCallback(updateMonitorCallbackArgumentCaptor.capture()) } @Test @@ -111,6 +119,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { @Test fun onResume() { + reset(keyguardUpdateMonitor) underTest.onResume(KeyguardSecurityView.VIEW_REVEALED) verify(keyguardUpdateMonitor) .registerCallback(any(KeyguardUpdateMonitorCallback::class.java)) @@ -137,4 +146,22 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { underTest.resetState() verify(keyguardMessageAreaController).setMessage("") } + + @Test + fun onSimStateChangedFromPinToPuk_showsCurrentSecurityScreen() { + updateMonitorCallbackArgumentCaptor.value.onSimStateChanged( + /* subId= */ 0, + /* slotId= */ 0, + TelephonyManager.SIM_STATE_PIN_REQUIRED + ) + verify(keyguardSecurityCallback, never()).showCurrentSecurityScreen() + + updateMonitorCallbackArgumentCaptor.value.onSimStateChanged( + /* subId= */ 0, + /* slotId= */ 0, + TelephonyManager.SIM_STATE_PUK_REQUIRED + ) + + verify(keyguardSecurityCallback).showCurrentSecurityScreen() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt new file mode 100644 index 000000000000..74f50d8c844b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt @@ -0,0 +1,171 @@ +/* + * 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.accessibility.data.repository + +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.util.settings.FakeSettings +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.test.StandardTestDispatcher +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 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class ColorCorrectionRepositoryImplTest : SysuiTestCase() { + companion object { + val TEST_USER_1 = UserHandle.of(1)!! + val TEST_USER_2 = UserHandle.of(2)!! + } + + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val settings: FakeSettings = FakeSettings() + + private lateinit var underTest: ColorCorrectionRepository + + @Before + fun setUp() { + underTest = + ColorCorrectionRepositoryImpl( + testDispatcher, + settings, + ) + } + + @Test + fun isEnabled_initiallyGetsSettingsValue() = + scope.runTest { + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + 1, + TEST_USER_1.identifier + ) + + underTest = + ColorCorrectionRepositoryImpl( + testDispatcher, + settings, + ) + + underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope) + runCurrent() + + val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first() + Truth.assertThat(actualValue).isTrue() + } + + @Test + fun isEnabled_settingUpdated_valueUpdated() = + scope.runTest { + underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope) + + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.DISABLED, + TEST_USER_1.identifier + ) + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse() + + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.ENABLED, + TEST_USER_1.identifier + ) + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue() + + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.DISABLED, + TEST_USER_1.identifier + ) + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse() + } + + @Test + fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = + scope.runTest { + underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope) + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.DISABLED, + TEST_USER_1.identifier + ) + underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope) + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.DISABLED, + TEST_USER_2.identifier + ) + + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse() + Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse() + + settings.putIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + ColorCorrectionRepositoryImpl.ENABLED, + TEST_USER_1.identifier + ) + runCurrent() + Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue() + Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse() + } + + @Test + fun setEnabled() = + scope.runTest { + val success = underTest.setIsEnabled(true, TEST_USER_1) + runCurrent() + Truth.assertThat(success).isTrue() + + val actualValue = + settings.getIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + TEST_USER_1.identifier + ) + Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED) + } + + @Test + fun setDisabled() = + scope.runTest { + val success = underTest.setIsEnabled(false, TEST_USER_1) + runCurrent() + Truth.assertThat(success).isTrue() + + val actualValue = + settings.getIntForUser( + ColorCorrectionRepositoryImpl.SETTING_NAME, + TEST_USER_1.identifier + ) + Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 2a02164224a4..08cd7edba6af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -21,9 +21,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat @@ -76,12 +76,13 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPin_succeeds() = testScope.runTest { - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test @@ -129,14 +130,15 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPassword_succeeds() = testScope.runTest { - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test @@ -185,7 +187,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.apply { setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) @@ -201,7 +203,8 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test @@ -262,7 +265,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNull() = + fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) @@ -270,7 +273,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.apply { setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) - setThrottleDuration(42) + setLockoutDuration(42) } val authResult = @@ -331,29 +334,30 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun isAutoConfirmEnabled_featureEnabledButDisabledByThrottling() = + fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) // The feature is enabled. assertThat(isAutoConfirmEnabled).isTrue() - // Make many wrong attempts to trigger throttling. - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { + // Make many wrong attempts to trigger lockout. + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN } - assertThat(throttling).isNotNull() + assertThat(lockout).isNotNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) - // Throttling disabled auto-confirm. + // Lockout disabled auto-confirm. assertThat(isAutoConfirmEnabled).isFalse() - // Move the clock forward one more second, to completely finish the throttling period: - advanceTimeBy(FakeAuthenticationRepository.THROTTLE_DURATION_MS + 1000L) - assertThat(throttling).isNull() + // Move the clock forward one more second, to completely finish the lockout period: + advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L) + assertThat(lockout).isNull() - // Auto-confirm is still disabled, because throttling occurred at least once in this + // Auto-confirm is still disabled, because lockout occurred at least once in this // session. assertThat(isAutoConfirmEnabled).isFalse() @@ -363,68 +367,70 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Auto-confirm is re-enabled. assertThat(isAutoConfirmEnabled).isTrue() + + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) } @Test - fun throttling() = + fun lockout() = testScope.runTest { - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) - assertThat(throttling).isNull() + assertThat(lockout).isNull() - // Make many wrong attempts, but just shy of what's needed to get throttled: - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) { + // Make many wrong attempts, but just shy of what's needed to get locked out: + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(throttling).isNull() + assertThat(lockout).isNull() } - // Make one more wrong attempt, leading to throttling: + // Make one more wrong attempt, leading to lockout: underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(throttling) + assertThat(lockout) .isEqualTo( - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS, + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, + remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, ) ) + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) - // Correct PIN, but throttled, so doesn't attempt it: + // Correct PIN, but locked out, so doesn't attempt it: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(throttling) + assertThat(lockout) .isEqualTo( - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS, + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, + remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, ) ) - // Move the clock forward to ALMOST skip the throttling, leaving one second to go: - val throttleTimeoutSec = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - repeat(FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - 1) { time -> + // Move the clock forward to ALMOST skip the lockout, leaving one second to go: + val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS + repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time -> advanceTimeBy(1000) - assertThat(throttling) + assertThat(lockout) .isEqualTo( - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = - FakeAuthenticationRepository - .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = throttleTimeoutSec - (time + 1), + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, + remainingSeconds = lockoutTimeoutSec - (time + 1), ) ) } - // Move the clock forward one more second, to completely finish the throttling period: + // Move the clock forward one more second, to completely finish the lockout period: advanceTimeBy(1000) - assertThat(throttling).isNull() + assertThat(lockout).isNull() - // Correct PIN and no longer throttled so unlocks successfully: + // Correct PIN and no longer locked out so unlocks successfully: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index cbb772f49c93..0ab596c82d6f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -21,6 +21,8 @@ import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel +import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF +import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl @@ -56,10 +58,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -539,4 +543,77 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : .onDozeAmountChanged(eq(0f), eq(0f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE)) job.cancel() } + + @Test + fun cancelledLockscreenToAod_dozeAmountNotUpdatedToZero() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForLockscreenAodTransitions(this) + // WHEN lockscreen to aod transition is cancelled + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.CANCELED + ) + ) + runCurrent() + + // THEN doze amount is NOT updated to zero + verify(mView, never()).onDozeAmountChanged(eq(0f), eq(0f), anyInt()) + job.cancel() + } + + @Test + fun dreamingToAod_dozeAmountChanged() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForDreamingToAodTransitions(this) + // WHEN dreaming to aod transition in progress + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.DREAMING, + to = KeyguardState.AOD, + value = .3f, + transitionState = TransitionState.RUNNING + ) + ) + runCurrent() + + // THEN doze amount is updated to + verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF)) + job.cancel() + } + + @Test + fun alternateBouncerToAod_dozeAmountChanged() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForAlternateBouncerToAodTransitions(this) + // WHEN alternate bouncer to aod transition in progress + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = .3f, + transitionState = TransitionState.RUNNING + ) + ) + runCurrent() + + // THEN doze amount is updated to + verify(mView) + .onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)) + job.cancel() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 3e3a1a947bd4..9b1df7c0ffc0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -21,9 +21,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.res.R @@ -246,46 +246,42 @@ class BouncerInteractorTest : SysuiTestCase() { } @Test - fun throttling() = + fun lockout() = testScope.runTest { - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(throttling).isNull() - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times -> + assertThat(lockout).isNull() + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times -> // Wrong PIN. assertThat(underTest.authenticate(listOf(6, 7, 8, 9))) .isEqualTo(AuthenticationResult.FAILED) - if ( - times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1 - ) { + if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) } } - assertThat(throttling) + assertThat(lockout) .isEqualTo( - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS, + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, + remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, ) ) assertTryAgainMessage( message, - FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds - .toInt() + FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt() ) - // Correct PIN, but throttled, so doesn't change away from the bouncer scene: + // Correct PIN, but locked out, so doesn't change away from the bouncer scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) assertTryAgainMessage( message, - FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds - .toInt() + FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt() ) - throttling?.remainingSeconds?.let { seconds -> + lockout?.remainingSeconds?.let { seconds -> repeat(seconds) { time -> advanceTimeBy(1000) val remainingTimeSec = seconds - time - 1 @@ -295,12 +291,12 @@ class BouncerInteractorTest : SysuiTestCase() { } } assertThat(message).isEqualTo("") - assertThat(throttling).isNull() + assertThat(lockout).isNull() - // Correct PIN and no longer throttled so changes to the Gone scene: + // Correct PIN and no longer locked out so changes to the Gone scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 45c186dc3a77..2f0843b202a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -35,7 +35,6 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() private val bouncerInteractor = utils.bouncerInteractor( authenticationInteractor = utils.authenticationInteractor(), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 2b64d8eca032..16a935943dbf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -135,17 +135,17 @@ class BouncerViewModelTest : SysuiTestCase() { fun message() = testScope.runTest { val message by collectLastValue(underTest.message) - val throttling by collectLastValue(bouncerInteractor.throttling) + val lockout by collectLastValue(bouncerInteractor.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(message?.isUpdateAnimated).isTrue() - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } assertThat(message?.isUpdateAnimated).isFalse() - throttling?.remainingSeconds?.let { remainingSeconds -> + lockout?.remainingSeconds?.let { remainingSeconds -> advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds) } assertThat(message?.isUpdateAnimated).isTrue() @@ -160,37 +160,37 @@ class BouncerViewModelTest : SysuiTestCase() { authViewModel?.isInputEnabled ?: emptyFlow() } ) - val throttling by collectLastValue(bouncerInteractor.throttling) + val lockout by collectLastValue(bouncerInteractor.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isInputEnabled).isTrue() - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } assertThat(isInputEnabled).isFalse() - throttling?.remainingSeconds?.let { remainingSeconds -> + lockout?.remainingSeconds?.let { remainingSeconds -> advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds) } assertThat(isInputEnabled).isTrue() } @Test - fun throttlingDialogMessage() = + fun dialogMessage() = testScope.runTest { - val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) + val dialogMessage by collectLastValue(underTest.dialogMessage) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { // Wrong PIN. - assertThat(throttlingDialogMessage).isNull() + assertThat(dialogMessage).isNull() bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } - assertThat(throttlingDialogMessage).isNotEmpty() + assertThat(dialogMessage).isNotEmpty() - underTest.onThrottlingDialogDismissed() - assertThat(throttlingDialogMessage).isNull() + underTest.onDialogDismissed() + assertThat(dialogMessage).isNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index a217d93c79ae..6d6baa57bb9d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.res.R @@ -243,12 +243,12 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } @Test - fun onImeVisibilityChanged_falseAfterTrue_whileThrottling_doesNothing() = + fun onImeVisibilityChanged_falseAfterTrue_whileLockedOut_doesNothing() = testScope.runTest { val events by collectValues(bouncerInteractor.onImeHiddenByUser) assertThat(events).isEmpty() underTest.onImeVisibilityChanged(isVisible = true) - setThrottling(true) + setLockout(true) underTest.onImeVisibilityChanged(isVisible = false) @@ -284,11 +284,11 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } @Test - fun isTextFieldFocusRequested_focusLostWhileThrottling_staysFalse() = + fun isTextFieldFocusRequested_focusLostWhileLockedOut_staysFalse() = testScope.runTest { val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) underTest.onTextFieldFocusChanged(isFocused = true) - setThrottling(true) + setLockout(true) underTest.onTextFieldFocusChanged(isFocused = false) @@ -296,14 +296,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } @Test - fun isTextFieldFocusRequested_throttlingCountdownEnds_becomesTrue() = + fun isTextFieldFocusRequested_lockoutCountdownEnds_becomesTrue() = testScope.runTest { val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) underTest.onTextFieldFocusChanged(isFocused = true) - setThrottling(true) + setLockout(true) underTest.onTextFieldFocusChanged(isFocused = false) - setThrottling(false) + setLockout(false) assertThat(isTextFieldFocusRequested).isTrue() } @@ -327,24 +327,24 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { switchToScene(SceneKey.Bouncer) } - private suspend fun TestScope.setThrottling( - isThrottling: Boolean, + private suspend fun TestScope.setLockout( + isLockedOut: Boolean, failedAttemptCount: Int = 5, ) { - if (isThrottling) { + if (isLockedOut) { repeat(failedAttemptCount) { authenticationRepository.reportAuthenticationAttempt(false) } val remainingTimeSeconds = 30 - authenticationRepository.setThrottleDuration(remainingTimeSeconds * 1000) - authenticationRepository.throttling.value = - AuthenticationThrottlingModel( + authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000) + authenticationRepository.lockout.value = + AuthenticationLockoutModel( failedAttemptCount = failedAttemptCount, remainingSeconds = remainingTimeSeconds, ) } else { authenticationRepository.reportAuthenticationAttempt(true) - authenticationRepository.throttling.value = null + authenticationRepository.lockout.value = null } runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 862c39c9d4cc..8971423edd52 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -304,13 +304,12 @@ class PatternBouncerViewModelTest : SysuiTestCase() { fun onDragEnd_whenPatternTooShort() = testScope.runTest { val message by collectLastValue(bouncerViewModel.message) - val throttlingDialogMessage by - collectLastValue(bouncerViewModel.throttlingDialogMessage) + val dialogMessage by collectLastValue(bouncerViewModel.dialogMessage) lockDeviceAndOpenPatternBouncer() // Enter a pattern that's too short more than enough times that would normally trigger - // throttling if the pattern were not too short and wrong: - val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING + 1 + // lockout if the pattern were not too short and wrong: + val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1 repeat(attempts) { attempt -> underTest.onDragStart() CORRECT_PATTERN.subList( @@ -328,7 +327,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragEnd() assertWithMessage("Attempt #$attempt").that(message?.text).isEqualTo(WRONG_PATTERN) - assertWithMessage("Attempt #$attempt").that(throttlingDialogMessage).isNull() + assertWithMessage("Attempt #$attempt").that(dialogMessage).isNull() } } 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/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 16cfa2398fd5..1f8e29adc983 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -161,7 +161,7 @@ class CommunalInteractorTest : SysuiTestCase() { whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java)) val targets = listOf(target1, target2, target3) - smartspaceRepository.setLockscreenSmartspaceTargets(targets) + smartspaceRepository.setCommunalSmartspaceTargets(targets) val smartspaceContent by collectLastValue(underTest.smartspaceContent) assertThat(smartspaceContent?.size).isEqualTo(1) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 8896e6e64bd9..314dfdfd6f2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -116,7 +116,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { whenever(target.smartspaceTargetId).thenReturn("target") whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) - smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) + smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) // Media playing. mediaRepository.mediaPlaying.value = true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 7fbcae0d8986..8a71168324aa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -135,7 +135,7 @@ class CommunalViewModelTest : SysuiTestCase() { whenever(target.smartspaceTargetId).thenReturn("target") whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER) whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java)) - smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target)) + smartspaceRepository.setCommunalSmartspaceTargets(listOf(target)) // Media playing. mediaRepository.mediaPlaying.value = true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 45aca175657e..d6d5b23a311d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -827,7 +827,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun isAuthenticatedIsResetToFalseWhenKeyguardIsGoingAway() = + fun isAuthenticatedIsResetToFalseWhenDeviceStartsGoingToSleep() = testScope.runTest { initCollectors() allPreconditionsToRunFaceAuthAreTrue() @@ -840,13 +840,13 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authenticated()).isTrue() - keyguardRepository.setKeyguardGoingAway(true) + powerInteractor.setAsleepForTest() assertThat(authenticated()).isFalse() } @Test - fun isAuthenticatedIsResetToFalseWhenDeviceStartsGoingToSleep() = + fun isAuthenticatedIsResetToFalseWhenDeviceGoesToSleep() = testScope.runTest { initCollectors() allPreconditionsToRunFaceAuthAreTrue() @@ -865,7 +865,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun isAuthenticatedIsResetToFalseWhenDeviceGoesToSleep() = + fun isAuthenticatedIsResetToFalseWhenUserIsSwitching() = testScope.runTest { initCollectors() allPreconditionsToRunFaceAuthAreTrue() @@ -878,13 +878,16 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authenticated()).isTrue() - powerInteractor.setAsleepForTest() + fakeUserRepository.setSelectedUserInfo( + primaryUser, + SelectionStatus.SELECTION_IN_PROGRESS + ) assertThat(authenticated()).isFalse() } @Test - fun isAuthenticatedIsResetToFalseWhenUserIsSwitching() = + fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() = testScope.runTest { initCollectors() allPreconditionsToRunFaceAuthAreTrue() @@ -897,10 +900,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authenticated()).isTrue() - fakeUserRepository.setSelectedUserInfo( - primaryUser, - SelectionStatus.SELECTION_IN_PROGRESS - ) + keyguardRepository.keyguardDoneAnimationsFinished() assertThat(authenticated()).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index bc4bae0ed959..34f703bc0ca7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -49,8 +49,10 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -93,6 +95,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { private lateinit var dockManager: DockManagerFake private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + private val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -179,6 +183,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = withDeps.keyguardInteractor, + shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, @@ -339,6 +344,31 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { } @Test + fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() = + testScope.runTest { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) + + repository.setKeyguardShowing(false) + repository.setIsDozing(true) + homeControls.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = ICON, + activationState = ActivationState.Active, + ) + ) + + val collectedValue by + collectLastValue( + underTest.quickAffordanceAlwaysVisible( + KeyguardQuickAffordancePosition.BOTTOM_START + ) + ) + + assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java) + } + + @Test fun quickAffordanceAlwaysVisible_evenWhenLockScreenNotShowingAndDozing() = testScope.runTest { repository.setKeyguardShowing(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt new file mode 100644 index 000000000000..e8504563b4ae --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt @@ -0,0 +1,40 @@ +package com.android.systemui.keyguard.ui.preview + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyguardRemotePreviewManagerTest : SysuiTestCase() { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Test + fun onDestroy_clearsReferencesToRenderer() = + testScope.runTest { + val renderer = mock<KeyguardPreviewRenderer>() + val onDestroy: (PreviewLifecycleObserver) -> Unit = {} + + val observer = PreviewLifecycleObserver(this, testDispatcher, renderer, onDestroy) + + // Precondition check. + assertThat(observer.renderer).isNotNull() + assertThat(observer.onDestroy).isNotNull() + + observer.onDestroy() + + // The verification checks renderer/requestDestruction lambda because they-re + // non-singletons which can't leak KeyguardPreviewRenderer. + assertThat(observer.renderer).isNull() + assertThat(observer.onDestroy).isNull() + } +} 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/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt new file mode 100644 index 000000000000..8ee6d2005350 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt @@ -0,0 +1,91 @@ +/* + * 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.tiles.impl.colorcorrection.domain + +import android.graphics.drawable.TestStubDrawable +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.android.systemui.qs.tiles.impl.colorcorrection.qsColorCorrectionTileConfig +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ColorCorrectionTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val colorCorrectionTileConfig = kosmos.qsColorCorrectionTileConfig + private val subtitleArray = + context.resources.getStringArray(R.array.tile_states_color_correction) + // Using lazy (versus =) to make sure we override the right context -- see b/311612168 + private val mapper by lazy { + ColorCorrectionTileMapper( + context.orCreateTestableResources + .apply { addOverride(R.drawable.ic_qs_color_correction, TestStubDrawable()) } + .resources, + context.theme + ) + } + + @Test + fun disabledModel() { + val inputModel = ColorCorrectionTileModel(false) + + val outputState = mapper.map(colorCorrectionTileConfig, inputModel) + + val expectedState = + createColorCorrectionTileState(QSTileState.ActivationState.INACTIVE, subtitleArray[1]) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel() { + val inputModel = ColorCorrectionTileModel(true) + + val outputState = mapper.map(colorCorrectionTileConfig, inputModel) + + val expectedState = + createColorCorrectionTileState(QSTileState.ActivationState.ACTIVE, subtitleArray[2]) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createColorCorrectionTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String + ): QSTileState { + val label = context.getString(R.string.quick_settings_color_correction_label) + return QSTileState( + { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + label, + null, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt new file mode 100644 index 000000000000..8c612acad887 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt @@ -0,0 +1,72 @@ +/* + * 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.tiles.impl.colorcorrection.domain.interactor + +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.accessibility.data.repository.FakeColorCorrectionRepository +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class ColorCorrectionTileDataInteractorTest : SysuiTestCase() { + + private val colorCorrectionRepository = FakeColorCorrectionRepository() + private val underTest: ColorCorrectionTileDataInteractor = + ColorCorrectionTileDataInteractor(colorCorrectionRepository) + + @Test + fun alwaysAvailable() = runTest { + val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) + + assertThat(availability).hasSize(1) + assertThat(availability.last()).isTrue() + } + + @Test + fun dataMatchesTheRepository() = runTest { + val dataList: List<ColorCorrectionTileModel> by + collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) + runCurrent() + + colorCorrectionRepository.setIsEnabled(true, TEST_USER) + runCurrent() + + colorCorrectionRepository.setIsEnabled(false, TEST_USER) + runCurrent() + + assertThat(dataList).hasSize(3) + assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false)) + } + + private companion object { + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..3049cc079a1c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt @@ -0,0 +1,89 @@ +/* + * 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.tiles.impl.colorcorrection.domain.interactor + +import android.os.UserHandle +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.FakeColorCorrectionRepository +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ColorCorrectionTileUserActionInteractorTest : SysuiTestCase() { + + private val testUser = UserHandle.CURRENT + private val repository = FakeColorCorrectionRepository() + private val inputHandler = FakeQSTileIntentUserInputHandler() + + private val underTest = + ColorCorrectionUserActionInteractor( + repository, + inputHandler, + ) + + @Test + fun handleClickWhenEnabled() = runTest { + val wasEnabled = true + repository.setIsEnabled(wasEnabled, testUser) + + underTest.handleInput(QSTileInputTestKtx.click(ColorCorrectionTileModel(wasEnabled))) + + assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled) + } + + @Test + fun handleClickWhenDisabled() = runTest { + val wasEnabled = false + repository.setIsEnabled(wasEnabled, testUser) + + underTest.handleInput(QSTileInputTestKtx.click(ColorCorrectionTileModel(wasEnabled))) + + assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled) + } + + @Test + fun handleLongClickWhenDisabled() = runTest { + val enabled = false + + underTest.handleInput(QSTileInputTestKtx.longClick(ColorCorrectionTileModel(enabled))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_COLOR_CORRECTION_SETTINGS) + } + } + + @Test + fun handleLongClickWhenEnabled() = runTest { + val enabled = true + + underTest.handleInput(QSTileInputTestKtx.longClick(ColorCorrectionTileModel(enabled))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_COLOR_CORRECTION_SETTINGS) + } + } +} 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/DataSaverDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt new file mode 100644 index 000000000000..4b9625107745 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt @@ -0,0 +1,101 @@ +/* + * 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.tiles.impl.saver.domain + +import android.content.SharedPreferences +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.leaks.FakeDataSaverController +import kotlin.coroutines.EmptyCoroutineContext +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify + +/** Test [DataSaverDialogDelegate]. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class DataSaverDialogDelegateTest : SysuiTestCase() { + + private val dataSaverController = FakeDataSaverController(LeakCheck()) + + private lateinit var sysuiDialogFactory: SystemUIDialog.Factory + private lateinit var sysuiDialog: SystemUIDialog + private lateinit var dataSaverDialogDelegate: DataSaverDialogDelegate + + @Before + fun setup() { + sysuiDialog = mock<SystemUIDialog>() + sysuiDialogFactory = mock<SystemUIDialog.Factory>() + + dataSaverDialogDelegate = + DataSaverDialogDelegate( + sysuiDialogFactory, + context, + EmptyCoroutineContext, + dataSaverController, + mock<SharedPreferences>() + ) + + whenever(sysuiDialogFactory.create(eq(dataSaverDialogDelegate), eq(context))) + .thenReturn(sysuiDialog) + } + @Test + fun delegateSetsDialogTitleCorrectly() { + val expectedResId = R.string.data_saver_enable_title + + dataSaverDialogDelegate.onCreate(sysuiDialog, null) + + verify(sysuiDialog).setTitle(eq(expectedResId)) + } + + @Test + fun delegateSetsDialogMessageCorrectly() { + val expectedResId = R.string.data_saver_description + + dataSaverDialogDelegate.onCreate(sysuiDialog, null) + + verify(sysuiDialog).setMessage(expectedResId) + } + + @Test + fun delegateSetsDialogPositiveButtonCorrectly() { + val expectedResId = R.string.data_saver_enable_button + + dataSaverDialogDelegate.onCreate(sysuiDialog, null) + + verify(sysuiDialog).setPositiveButton(eq(expectedResId), any()) + } + + @Test + fun delegateSetsDialogCancelButtonCorrectly() { + val expectedResId = R.string.cancel + + dataSaverDialogDelegate.onCreate(sysuiDialog, null) + + verify(sysuiDialog).setNeutralButton(eq(expectedResId), eq(null)) + } +} 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 new file mode 100644 index 000000000000..d162c778f607 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt @@ -0,0 +1,106 @@ +/* + * 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.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 +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.qs.tiles.impl.saver.qsDataSaverTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DataSaverTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + 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 + .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() { + val inputModel = DataSaverTileModel(true) + + val outputState = mapper.map(dataSaverTileConfig, inputModel) + + val expectedState = + createDataSaverTileState( + QSTileState.ActivationState.ACTIVE, + R.drawable.qs_data_saver_icon_on + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun inactiveStateMatchesDisabledModel() { + val inputModel = DataSaverTileModel(false) + + val outputState = mapper.map(dataSaverTileConfig, inputModel) + + val expectedState = + createDataSaverTileState( + QSTileState.ActivationState.INACTIVE, + R.drawable.qs_data_saver_icon_off + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createDataSaverTileState( + activationState: QSTileState.ActivationState, + iconRes: Int + ): QSTileState { + val label = context.getString(R.string.data_saver) + val secondaryLabel = + if (activationState == QSTileState.ActivationState.ACTIVE) + context.resources.getStringArray(R.array.tile_states_saver)[2] + else if (activationState == QSTileState.ActivationState.INACTIVE) + context.resources.getStringArray(R.array.tile_states_saver)[1] + else context.resources.getStringArray(R.array.tile_states_saver)[0] + + return QSTileState( + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + label, + null, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt new file mode 100644 index 000000000000..819bd03437f4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt @@ -0,0 +1,75 @@ +/* + * 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.tiles.impl.saver.domain.interactor + +import android.os.UserHandle +import android.testing.LeakCheck +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.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.utils.leaks.FakeDataSaverController +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class DataSaverTileDataInteractorTest : SysuiTestCase() { + private val controller: FakeDataSaverController = FakeDataSaverController(LeakCheck()) + private val underTest: DataSaverTileDataInteractor = DataSaverTileDataInteractor(controller) + + @Test + fun isAvailableRegardlessOfController() = runTest { + controller.setDataSaverEnabled(false) + + runCurrent() + val availability by collectLastValue(underTest.availability(TEST_USER)) + + Truth.assertThat(availability).isTrue() + } + + @Test + fun dataMatchesController() = runTest { + controller.setDataSaverEnabled(false) + val flowValues: List<DataSaverTileModel> by + collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) + + runCurrent() + controller.setDataSaverEnabled(true) + runCurrent() + controller.setDataSaverEnabled(false) + runCurrent() + + Truth.assertThat(flowValues.size).isEqualTo(3) + Truth.assertThat(flowValues.map { it.isEnabled }) + .containsExactly(false, true, false) + .inOrder() + } + + private companion object { + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..7091cb3b259c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt @@ -0,0 +1,183 @@ +/* + * 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.tiles.impl.saver.domain.interactor + +import android.content.Context +import android.content.SharedPreferences +import android.provider.Settings +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.intentInputs +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.settings.UserFileManager +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.leaks.FakeDataSaverController +import com.google.common.truth.Truth.assertThat +import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DataSaverTileUserActionInteractorTest : SysuiTestCase() { + private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler() + private val dataSaverController = FakeDataSaverController(LeakCheck()) + + private lateinit var userFileManager: UserFileManager + private lateinit var sharedPreferences: SharedPreferences + private lateinit var dialogFactory: SystemUIDialog.Factory + private lateinit var underTest: DataSaverTileUserActionInteractor + + @Before + fun setup() { + userFileManager = mock<UserFileManager>() + sharedPreferences = mock<SharedPreferences>() + dialogFactory = mock<SystemUIDialog.Factory>() + whenever( + userFileManager.getSharedPreferences( + eq(DataSaverTileUserActionInteractor.PREFS), + eq(Context.MODE_PRIVATE), + eq(context.userId) + ) + ) + .thenReturn(sharedPreferences) + + underTest = + DataSaverTileUserActionInteractor( + context, + EmptyCoroutineContext, + EmptyCoroutineContext, + dataSaverController, + qsTileIntentUserActionHandler, + mock<DialogLaunchAnimator>(), + dialogFactory, + userFileManager, + ) + } + + /** Since the dialog was shown before, we expect the click to enable the controller. */ + @Test + fun handleClickToEnableDialogShownBefore() = runTest { + whenever( + sharedPreferences.getBoolean( + eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN), + any() + ) + ) + .thenReturn(true) + val stateBeforeClick = false + + underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick))) + + assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!stateBeforeClick) + } + + /** + * The first time the tile is clicked to turn on we expect (1) the enabled state to not change + * and (2) the dialog to be shown instead. + */ + @Test + fun handleClickToEnableDialogNotShownBefore() = runTest { + whenever( + sharedPreferences.getBoolean( + eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN), + any() + ) + ) + .thenReturn(false) + val mockDialog = mock<SystemUIDialog>() + whenever(dialogFactory.create(any(), any())).thenReturn(mockDialog) + val stateBeforeClick = false + + val input = QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick)) + underTest.handleInput(input) + + assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(stateBeforeClick) + verify(mockDialog).show() + } + + /** Disabling should flip the state, even if the dialog was not shown before. */ + @Test + fun handleClickToDisableDialogNotShownBefore() = runTest { + whenever( + sharedPreferences.getBoolean( + eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN), + any() + ) + ) + .thenReturn(false) + val enabledBeforeClick = true + + underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick))) + + assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick) + } + + @Test + fun handleClickToDisableDialogShownBefore() = runTest { + whenever( + sharedPreferences.getBoolean( + eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN), + any() + ) + ) + .thenReturn(true) + val enabledBeforeClick = true + + underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick))) + + assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick) + } + + @Test + fun handleLongClickWhenEnabled() = runTest { + val enabledState = true + + underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState))) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } + + @Test + fun handleLongClickWhenDisabled() = runTest { + val enabledState = false + + underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState))) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } +} 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/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt new file mode 100644 index 000000000000..ef2046d85a14 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt @@ -0,0 +1,172 @@ +/* + * 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.smartspace + +import android.app.smartspace.SmartspaceManager +import android.app.smartspace.SmartspaceSession +import android.app.smartspace.SmartspaceTarget +import android.content.Context +import android.graphics.drawable.Drawable +import android.testing.TestableLooper +import android.view.View +import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.smartspace.CommunalSmartspaceController +import com.android.systemui.plugins.BcSmartspaceConfigPlugin +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.util.concurrency.Execution +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import java.util.concurrent.Executor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +class CommunalSmartspaceControllerTest : SysuiTestCase() { + @Mock private lateinit var smartspaceManager: SmartspaceManager + + @Mock private lateinit var execution: Execution + + @Mock private lateinit var uiExecutor: Executor + + @Mock private lateinit var targetFilter: SmartspaceTargetFilter + + @Mock private lateinit var plugin: BcSmartspaceDataPlugin + + @Mock private lateinit var precondition: SmartspacePrecondition + + @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener + + @Mock private lateinit var session: SmartspaceSession + + private lateinit var controller: CommunalSmartspaceController + + // TODO(b/272811280): Remove usage of real view + private val fakeParent = FrameLayout(context) + + /** + * A class which implements SmartspaceView and extends View. This is mocked to provide the right + * object inheritance and interface implementation used in CommunalSmartspaceController + */ + private class TestView(context: Context?) : View(context), SmartspaceView { + override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {} + + override fun registerConfigProvider(plugin: BcSmartspaceConfigPlugin?) {} + + override fun setPrimaryTextColor(color: Int) {} + + override fun setUiSurface(uiSurface: String) {} + + override fun setDozeAmount(amount: Float) {} + + override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {} + + override fun setFalsingManager(falsingManager: FalsingManager?) {} + + override fun setDnd(image: Drawable?, description: String?) {} + + override fun setNextAlarm(image: Drawable?, description: String?) {} + + override fun setMediaTarget(target: SmartspaceTarget?) {} + + override fun getSelectedPage(): Int { + return 0 + } + + override fun getCurrentCardTopPadding(): Int { + return 0 + } + } + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session) + + controller = + CommunalSmartspaceController( + context, + smartspaceManager, + execution, + uiExecutor, + precondition, + Optional.of(targetFilter), + Optional.of(plugin) + ) + } + + /** Ensures smartspace session begins on a listener only flow. */ + @Test + fun testConnectOnListen() { + `when`(precondition.conditionsMet()).thenReturn(true) + controller.addListener(listener) + + verify(smartspaceManager).createSmartspaceSession(any()) + + var targetListener = + withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> { + verify(session).addOnTargetsAvailableListener(any(), capture()) + } + + `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true) + + var target = Mockito.mock(SmartspaceTarget::class.java) + targetListener.onTargetsAvailable(listOf(target)) + + var targets = + withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) } + + assertThat(targets.contains(target)).isTrue() + + controller.removeListener(listener) + + verify(session).close() + } + + /** + * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace + * view is detached. + */ + @Test + fun testDisconnect_emitsEmptyListAndRemovesNotifier() { + `when`(precondition.conditionsMet()).thenReturn(true) + controller.addListener(listener) + + verify(smartspaceManager).createSmartspaceSession(any()) + + controller.removeListener(listener) + + verify(session).close() + + // And the listener receives an empty list of targets and unregisters the notifier + verify(plugin).onTargetsAvailable(emptyList()) + verify(plugin).registerSmartspaceEventNotifier(null) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index 28f3e93b6783..4cdb08afb22e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -67,9 +67,24 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { val bounds by collectLastValue(appearanceViewModel.stackBounds) val top = 200f + val left = 0f val bottom = 550f - placeholderViewModel.onBoundsChanged(top, bottom) - assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) + val right = 100f + placeholderViewModel.onBoundsChanged( + left = left, + top = top, + right = right, + bottom = bottom + ) + assertThat(bounds) + .isEqualTo( + NotificationContainerBounds( + left = left, + top = top, + right = right, + bottom = bottom + ) + ) } @Test diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index 64c0f99f4ba7..c99cb39f91bf 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -44,6 +44,7 @@ public interface BcSmartspaceDataPlugin extends Plugin { String UI_SURFACE_HOME_SCREEN = "home"; String UI_SURFACE_MEDIA = "media_data_manager"; String UI_SURFACE_DREAM = "dream"; + String UI_SURFACE_GLANCEABLE_HUB = "glanceable_hub"; String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA"; int VERSION = 1; 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/drawable/arrow_pointing_down.xml b/packages/SystemUI/res/drawable/arrow_pointing_down.xml new file mode 100644 index 000000000000..be39683cd78d --- /dev/null +++ b/packages/SystemUI/res/drawable/arrow_pointing_down.xml @@ -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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml new file mode 100644 index 000000000000..bd604317bbb8 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml @@ -0,0 +1,25 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z" + android:fillColor="#000000"/> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml new file mode 100644 index 000000000000..fadaf7896ae4 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml @@ -0,0 +1,25 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 66c57fc2a9ac..6d7ce0623817 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -106,5 +106,5 @@ </FrameLayout> <include layout="@layout/ambient_indication" - android:id="@+id/ambient_indication_container" /> + android:id="@id/ambient_indication_container" /> </com.android.systemui.statusbar.phone.KeyguardBottomAreaView> diff --git a/packages/SystemUI/res/layout/privacy_dialog_card_button.xml b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml index e297b939e2b8..bcbe2c435509 100644 --- a/packages/SystemUI/res/layout/privacy_dialog_card_button.xml +++ b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml @@ -17,6 +17,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="56dp" + android:paddingTop="4dp" + android:paddingBottom="4dp" android:layout_marginBottom="4dp" android:ellipsize="end" android:maxLines="1" diff --git a/packages/SystemUI/res/layout/privacy_dialog_v2.xml b/packages/SystemUI/res/layout/privacy_dialog_v2.xml index 843dad03bca4..76098a1ab486 100644 --- a/packages/SystemUI/res/layout/privacy_dialog_v2.xml +++ b/packages/SystemUI/res/layout/privacy_dialog_v2.xml @@ -16,7 +16,7 @@ <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:layout_width="@dimen/large_dialog_width" + android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml new file mode 100644 index 000000000000..53ad9f157a2e --- /dev/null +++ b/packages/SystemUI/res/layout/record_issue_dialog.xml @@ -0,0 +1,81 @@ +<?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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="vertical" > + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:textAppearance="@style/TextAppearance.Dialog.Body.Message" + android:text="@string/qs_record_issue_dropdown_header" /> + + <Button + android:id="@+id/issue_type_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/qs_record_issue_dropdown_prompt" + android:lines="1" + android:drawableRight="@drawable/arrow_pointing_down" + android:layout_marginTop="@dimen/qqs_layout_margin_top" + android:focusable="false" + android:clickable="true" /> + + <!-- Screen Record Switch --> + <LinearLayout + android:id="@+id/screenrecord_switch_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/qqs_layout_margin_top" + android:orientation="horizontal"> + + <ImageView + android:layout_width="@dimen/screenrecord_option_icon_size" + android:layout_height="@dimen/screenrecord_option_icon_size" + android:layout_weight="0" + android:src="@drawable/ic_screenrecord" + app:tint="?androidprv:attr/materialColorOnSurface" + android:layout_gravity="center" + android:layout_marginEnd="@dimen/screenrecord_option_padding" /> + + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:minHeight="@dimen/screenrecord_option_icon_size" + android:layout_weight="1" + android:layout_gravity="fill_vertical" + android:gravity="center" + android:text="@string/quick_settings_screen_record_label" + android:textAppearance="@style/TextAppearance.Dialog.Body.Message" + android:importantForAccessibility="no"/> + + <Switch + android:id="@+id/screenrecord_switch" + android:layout_width="wrap_content" + android:minHeight="@dimen/screenrecord_option_icon_size" + android:layout_height="wrap_content" + android:gravity="center" + android:layout_gravity="fill_vertical" + android:layout_weight="0" + android:contentDescription="@string/quick_settings_screen_record_label" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file 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/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 1838795a57d6..cf63cc74521d 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -223,6 +223,8 @@ <item type="id" name="lock_icon_bg" /> <item type="id" name="burn_in_layer" /> <item type="id" name="communal_tutorial_indicator" /> + <item type="id" name="nssl_placeholder_barrier_bottom" /> + <item type="id" name="ambient_indication_container" /> <!-- Privacy dialog --> <item type="id" name="privacy_dialog_close_app_button" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 7ca0b6ee8d9f..e10925d551e2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -824,6 +824,27 @@ <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] --> <string name="quick_settings_screen_record_stop">Stop</string> + <!-- QuickSettings: Record Issue tile [CHAR LIMIT=NONE] --> + <string name="qs_record_issue_label">Record Issue</string> + <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] --> + <string name="qs_record_issue_start">Start</string> + <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] --> + <string name="qs_record_issue_stop">Stop</string> + + <!-- QuickSettings: Issue Type Drop down options in Record Issue Start Dialog [CHAR LIMIT=50] --> + <string name="qs_record_issue_dropdown_header">What part of your device experience was affected?</string> + <!-- QuickSettings: Issue Type Drop down prompt in Record Issue Start Dialog [CHAR LIMIT=30] --> + <string name="qs_record_issue_dropdown_prompt">Select issue type</string> + <!-- QuickSettings: Screen record switch label in Record Issue Start Dialog [CHAR LIMIT=20] --> + <string name="qs_record_issue_dropdown_screenrecord">Screen record</string> + + <!-- QuickSettings: Issue Type Drop down choices list in Record Issue Start Dialog [CHAR LIMIT=30] --> + <string-array name="qs_record_issue_types"> + <item>Performance</item> + <item>User Interface</item> + <item>Battery</item> + </string-array> + <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] --> <string name="quick_settings_onehanded_label">One-handed mode</string> @@ -1059,9 +1080,11 @@ <!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] --> <string name="button_to_open_widget_editor">Open the widget editor</string> <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] --> - <string name="button_to_remove_widget">Remove a widget</string> + <string name="button_to_remove_widget">Remove</string> <!-- Text for the button that launches the hub mode widget picker. [CHAR LIMIT=50] --> - <string name="hub_mode_add_widget_button_text">Add Widget</string> + <string name="hub_mode_add_widget_button_text">Add widget</string> + <!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] --> + <string name="hub_mode_editing_exit_button_text">Done</string> <!-- Related to user switcher --><skip/> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt index 80f70a0cd2f2..30648291366a 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt @@ -37,6 +37,10 @@ data class BiometricModalities( val hasSfps: Boolean get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType + /** If UDFPS authentication is available. */ + val hasUdfps: Boolean + get() = hasFingerprint && fingerprintProperties!!.isAnyUdfpsType + /** If fingerprint authentication is available (and [faceProperties] is non-null). */ val hasFace: Boolean get() = faceProperties != null diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index fec96c675b22..317201d2c2d9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -16,8 +16,6 @@ package com.android.systemui.shared.rotation; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; - import android.annotation.DimenRes; import android.annotation.IdRes; import android.annotation.LayoutRes; @@ -89,8 +87,7 @@ public class FloatingRotationButton implements RotationButton { @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin, @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter, @DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) { - mContext = context.createWindowContext(context.getDisplay(), TYPE_NAVIGATION_BAR_PANEL, - null); + mContext = context; mWindowManager = mContext.getSystemService(WindowManager.class); mKeyButtonContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(layout, null); mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java index c505bd502985..0169f59a5a20 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java @@ -21,6 +21,7 @@ import android.os.Build; import android.text.TextUtils; import android.view.View; +import com.android.internal.jank.Cuj; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; @@ -29,40 +30,26 @@ import java.lang.annotation.RetentionPolicy; public final class InteractionJankMonitorWrapper { // Launcher journeys. - public static final int CUJ_APP_LAUNCH_FROM_RECENTS = - InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS; - public static final int CUJ_APP_LAUNCH_FROM_ICON = - InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON; - public static final int CUJ_APP_CLOSE_TO_HOME = - InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME; + public static final int CUJ_APP_LAUNCH_FROM_RECENTS = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS; + public static final int CUJ_APP_LAUNCH_FROM_ICON = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON; + public static final int CUJ_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME; public static final int CUJ_APP_CLOSE_TO_HOME_FALLBACK = - InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; - public static final int CUJ_APP_CLOSE_TO_PIP = - InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP; - public static final int CUJ_QUICK_SWITCH = - InteractionJankMonitor.CUJ_LAUNCHER_QUICK_SWITCH; - public static final int CUJ_OPEN_ALL_APPS = - InteractionJankMonitor.CUJ_LAUNCHER_OPEN_ALL_APPS; - public static final int CUJ_CLOSE_ALL_APPS_SWIPE = - InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE; - public static final int CUJ_CLOSE_ALL_APPS_TO_HOME = - InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME; - public static final int CUJ_ALL_APPS_SCROLL = - InteractionJankMonitor.CUJ_LAUNCHER_ALL_APPS_SCROLL; - public static final int CUJ_APP_LAUNCH_FROM_WIDGET = - InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET; - public static final int CUJ_SPLIT_SCREEN_ENTER = - InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER; + Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; + public static final int CUJ_APP_CLOSE_TO_PIP = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP; + public static final int CUJ_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH; + public static final int CUJ_OPEN_ALL_APPS = Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS; + public static final int CUJ_CLOSE_ALL_APPS_SWIPE = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE; + public static final int CUJ_CLOSE_ALL_APPS_TO_HOME = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME; + public static final int CUJ_ALL_APPS_SCROLL = Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL; + public static final int CUJ_APP_LAUNCH_FROM_WIDGET = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET; + public static final int CUJ_SPLIT_SCREEN_ENTER = Cuj.CUJ_SPLIT_SCREEN_ENTER; public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = - InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; - public static final int CUJ_RECENTS_SCROLLING = - InteractionJankMonitor.CUJ_RECENTS_SCROLLING; - public static final int CUJ_APP_SWIPE_TO_RECENTS = - InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS; - public static final int CUJ_OPEN_SEARCH_RESULT = - InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT; - public static final int CUJ_LAUNCHER_UNFOLD_ANIM = - InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM; + Cuj.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; + public static final int CUJ_RECENTS_SCROLLING = Cuj.CUJ_RECENTS_SCROLLING; + public static final int CUJ_APP_SWIPE_TO_RECENTS = Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS; + public static final int CUJ_OPEN_SEARCH_RESULT = Cuj.CUJ_LAUNCHER_OPEN_SEARCH_RESULT; + public static final int CUJ_LAUNCHER_UNFOLD_ANIM = Cuj.CUJ_LAUNCHER_UNFOLD_ANIM; + public static final int CUJ_SEARCH_QSB_OPEN = Cuj.CUJ_LAUNCHER_SEARCH_QSB_OPEN; @IntDef({ CUJ_APP_LAUNCH_FROM_RECENTS, @@ -80,6 +67,7 @@ public final class InteractionJankMonitorWrapper { CUJ_CLOSE_ALL_APPS_TO_HOME, CUJ_OPEN_SEARCH_RESULT, CUJ_LAUNCHER_UNFOLD_ANIM, + CUJ_SEARCH_QSB_OPEN, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -89,7 +77,7 @@ public final class InteractionJankMonitorWrapper { * Begin a trace session. * * @param v an attached view. - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. */ public static void begin(View v, @CujType int cujType) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return; @@ -100,7 +88,7 @@ public final class InteractionJankMonitorWrapper { * Begin a trace session. * * @param v an attached view. - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. * @param timeout duration to cancel the instrumentation in ms */ public static void begin(View v, @CujType int cujType, long timeout) { @@ -115,7 +103,7 @@ public final class InteractionJankMonitorWrapper { * Begin a trace session. * * @param v an attached view. - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. * @param tag the tag to distinguish different flow of same type CUJ. */ public static void begin(View v, @CujType int cujType, String tag) { @@ -131,7 +119,7 @@ public final class InteractionJankMonitorWrapper { /** * End a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. */ public static void end(@CujType int cujType) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index bf688698de5f..d8c1e41465db 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -103,8 +103,6 @@ public class QuickStepContract { public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22; // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it. public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23; - // The current app is in immersive mode - public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24; // The voice interaction session window is showing public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25; // Freeform windows are showing in desktop mode @@ -162,7 +160,6 @@ public class QuickStepContract { SYSUI_STATE_DEVICE_DOZING, SYSUI_STATE_BACK_DISABLED, SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, - SYSUI_STATE_IMMERSIVE_MODE, SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, SYSUI_STATE_DEVICE_DREAMING, @@ -247,9 +244,6 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0) { str.add("bubbles_mange_menu_expanded"); } - if ((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0) { - str.add("immersive_mode"); - } if ((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) { str.add("vis_win_showing"); } 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/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java index 38a8cd39a078..c4aa7a26be18 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java @@ -104,4 +104,14 @@ public interface KeyguardSecurityCallback { */ default void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { } + + /** + * Shows the security screen that should be shown. + * + * This can be considered as a "refresh" of the bouncer view. Based on certain parameters, + * we might switch to a different bouncer screen. e.g. SimPin to SimPuk. + */ + default void showCurrentSecurityScreen() { + + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index f706301df1ca..0a4378e07b45 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -27,6 +27,8 @@ import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY; import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER; import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE; +import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPin; +import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPuk; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES; @@ -99,6 +101,7 @@ import com.android.systemui.util.settings.GlobalSettings; import dagger.Lazy; import java.io.File; +import java.util.Arrays; import java.util.Optional; import javax.inject.Inject; @@ -164,8 +167,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } mCurrentUser = mSelectedUserInteractor.getSelectedUserId(); showPrimarySecurityScreen(false); - if (mCurrentSecurityMode != SecurityMode.SimPin - && mCurrentSecurityMode != SecurityMode.SimPuk) { + if (mCurrentSecurityMode != SimPin + && mCurrentSecurityMode != SimPuk) { reinflateViewFlipper((l) -> { }); } @@ -334,6 +337,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { mViewMediatorCallback.setNeedsInput(needsInput); } + + @Override + public void showCurrentSecurityScreen() { + showPrimarySecurityScreen(false); + } }; private final SwipeListener mSwipeListener = new SwipeListener() { @@ -888,7 +896,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard finish = true; eventSubtype = BOUNCER_DISMISS_SIM; uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; - } else { + } else if (Arrays.asList(SimPin, SimPuk).contains(securityMode)) { + // There are additional screens to the sim pin/puk flow. showSecurityScreen(securityMode); } break; @@ -1095,8 +1104,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } private void configureMode() { - boolean useSimSecurity = mCurrentSecurityMode == SecurityMode.SimPin - || mCurrentSecurityMode == SecurityMode.SimPuk; + boolean useSimSecurity = mCurrentSecurityMode == SimPin + || mCurrentSecurityMode == SimPuk; int mode = KeyguardSecurityContainer.MODE_DEFAULT; if (canDisplayUserSwitcher() && !useSimSecurity) { mode = KeyguardSecurityContainer.MODE_USER_SWITCHER; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 6e242084d68c..c5e70703cd2b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; import android.annotation.NonNull; @@ -60,7 +62,7 @@ public class KeyguardSimPinViewController // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would // be displayed to inform user about the number of remaining PIN attempts left. private boolean mShowDefaultMessage; - private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private int mSubId = INVALID_SUBSCRIPTION_ID; private AlertDialog mRemainingAttemptsDialog; private ImageView mSimImageView; @@ -68,6 +70,12 @@ public class KeyguardSimPinViewController @Override public void onSimStateChanged(int subId, int slotId, int simState) { if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + // If subId has gone to PUK required then we need to go to the PUK screen. + if (subId == mSubId && simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) { + getKeyguardSecurityCallback().showCurrentSecurityScreen(); + return; + } + if (simState == TelephonyManager.SIM_STATE_READY) { mRemainingAttempts = -1; resetState(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 37bd9b287ebd..9c61a8a3cd8b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1277,6 +1277,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final FaceAuthenticationListener mFaceAuthenticationListener = new FaceAuthenticationListener() { + public void onAuthenticatedChanged(boolean isAuthenticated) { + if (!isAuthenticated) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onFacesCleared(); + } + } + } + } + @Override public void onAuthEnrollmentStateChanged(boolean enrolled) { notifyAboutEnrollmentChange(TYPE_FACE); @@ -1961,7 +1972,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected void handleStartedGoingToSleep(int arg1) { Assert.isMainThread(); - clearBiometricRecognized(); + clearFingerprintRecognized(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -3010,7 +3021,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab void handleUserSwitching(int userId, Runnable resultCallback) { mLogger.logUserSwitching(userId, "from UserTracker"); Assert.isMainThread(); - clearBiometricRecognized(); + clearFingerprintRecognized(); boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId); mLogger.logTrustUsuallyManagedUpdated(userId, mUserTrustIsUsuallyManaged.get(userId), trustUsuallyManaged, "userSwitching"); @@ -3560,25 +3571,30 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mServiceStates.get(subId); } - public void clearBiometricRecognized() { - clearBiometricRecognized(UserHandle.USER_NULL); + /** + * Resets the fingerprint authenticated state to false. + */ + public void clearFingerprintRecognized() { + clearFingerprintRecognized(UserHandle.USER_NULL); } - public void clearBiometricRecognizedWhenKeyguardDone(int unlockedUser) { - clearBiometricRecognized(unlockedUser); + /** + * Resets the fingerprint authenticated state to false. + */ + public void clearFingerprintRecognizedWhenKeyguardDone(int unlockedUser) { + clearFingerprintRecognized(unlockedUser); } - private void clearBiometricRecognized(int unlockedUser) { + private void clearFingerprintRecognized(int unlockedUser) { Assert.isMainThread(); mUserFingerprintAuthenticated.clear(); mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser); - mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser); - mLogger.d("clearBiometricRecognized"); + mLogger.d("clearFingerprintRecognized"); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricsCleared(); + cb.onFingerprintsCleared(); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 02dd3312c587..9d216dcede2b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -291,9 +291,14 @@ public class KeyguardUpdateMonitorCallback { public void onLogoutEnabledChanged() { } /** - * Called when authenticated biometrics are cleared. + * Called when authenticated fingerprint biometrics are cleared. */ - public void onBiometricsCleared() { } + public void onFingerprintsCleared() { } + + /** + * Called when authenticated face biometrics have cleared. + */ + public void onFacesCleared() { } /** * Called when the secondary lock screen requirement changes. diff --git a/packages/SystemUI/src/com/android/systemui/StatusBarInsetsCommand.kt b/packages/SystemUI/src/com/android/systemui/StatusBarInsetsCommand.kt new file mode 100644 index 000000000000..7e2a1e1e5618 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/StatusBarInsetsCommand.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 + +import android.view.Surface +import android.view.Surface.Rotation +import com.android.systemui.statusbar.commandline.ParseableCommand +import com.android.systemui.statusbar.commandline.Type +import java.io.PrintWriter + +class StatusBarInsetsCommand( + private val callback: Callback, +) : ParseableCommand(NAME) { + + val bottomMargin: BottomMarginCommand? by subCommand(BottomMarginCommand()) + + override fun execute(pw: PrintWriter) { + callback.onExecute(command = this, pw) + } + + interface Callback { + fun onExecute(command: StatusBarInsetsCommand, printWriter: PrintWriter) + } + + companion object { + const val NAME = "status-bar-insets" + } +} + +class BottomMarginCommand : ParseableCommand(NAME) { + + private val rotationDegrees: Int? by + param( + longName = "rotation", + shortName = "r", + description = "For which rotation the margin should be set. One of 0, 90, 180, 270", + valueParser = Type.Int, + ) + + @Rotation + val rotationValue: Int? + get() = ROTATION_DEGREES_TO_VALUE_MAPPING[rotationDegrees] + + val marginBottomDp: Float? by + param( + longName = "margin", + shortName = "m", + description = "Margin amount, in dp. Can be a fractional value, such as 10.5", + valueParser = Type.Float, + ) + + override fun execute(pw: PrintWriter) { + // Not needed for a subcommand + } + + companion object { + const val NAME = "bottom-margin" + private val ROTATION_DEGREES_TO_VALUE_MAPPING = + mapOf( + 0 to Surface.ROTATION_0, + 90 to Surface.ROTATION_90, + 180 to Surface.ROTATION_180, + 270 to Surface.ROTATION_270, + ) + + val ROTATION_DEGREES_OPTIONS: Set<Int> = ROTATION_DEGREES_TO_VALUE_MAPPING.keys + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt index ca859de73a36..24aa11e10f30 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -16,61 +16,16 @@ package com.android.systemui.accessibility -import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.qs.tiles.ColorCorrectionTile -import com.android.systemui.qs.tiles.ColorInversionTile -import com.android.systemui.qs.tiles.DreamTile -import com.android.systemui.qs.tiles.FontScalingTile -import com.android.systemui.qs.tiles.NightDisplayTile -import com.android.systemui.qs.tiles.OneHandedModeTile -import com.android.systemui.qs.tiles.ReduceBrightColorsTile +import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository +import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl +import com.android.systemui.accessibility.qs.QSAccessibilityModule import dagger.Binds import dagger.Module -import dagger.multibindings.IntoMap -import dagger.multibindings.StringKey -@Module +@Module(includes = [QSAccessibilityModule::class]) interface AccessibilityModule { - - /** Inject ColorInversionTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(ColorInversionTile.TILE_SPEC) - fun bindColorInversionTile(colorInversionTile: ColorInversionTile): QSTileImpl<*> - - /** Inject NightDisplayTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(NightDisplayTile.TILE_SPEC) - fun bindNightDisplayTile(nightDisplayTile: NightDisplayTile): QSTileImpl<*> - - /** Inject ReduceBrightColorsTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(ReduceBrightColorsTile.TILE_SPEC) - fun bindReduceBrightColorsTile(reduceBrightColorsTile: ReduceBrightColorsTile): QSTileImpl<*> - - /** Inject OneHandedModeTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(OneHandedModeTile.TILE_SPEC) - fun bindOneHandedModeTile(oneHandedModeTile: OneHandedModeTile): QSTileImpl<*> - - /** Inject ColorCorrectionTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(ColorCorrectionTile.TILE_SPEC) - fun bindColorCorrectionTile(colorCorrectionTile: ColorCorrectionTile): QSTileImpl<*> - - /** Inject DreamTile into tileMap in QSModule */ - @Binds - @IntoMap - @StringKey(DreamTile.TILE_SPEC) - fun bindDreamTile(dreamTile: DreamTile): QSTileImpl<*> - - /** Inject FontScalingTile into tileMap in QSModule */ @Binds - @IntoMap - @StringKey(FontScalingTile.TILE_SPEC) - fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*> + abstract fun colorCorrectionRepository( + impl: ColorCorrectionRepositoryImpl + ): ColorCorrectionRepository } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java index 4944531989d3..ba943b07b704 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java @@ -22,8 +22,8 @@ import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.view.accessibility.IMagnificationConnection; +import android.view.accessibility.IMagnificationConnectionCallback; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnectionCallback; import com.android.systemui.dagger.qualifiers.Main; @@ -36,7 +36,7 @@ class MagnificationConnectionImpl extends IMagnificationConnection.Stub { private static final String TAG = "WindowMagnificationConnectionImpl"; - private IWindowMagnificationConnectionCallback mConnectionCallback; + private IMagnificationConnectionCallback mConnectionCallback; private final Magnification mMagnification; private final Handler mHandler; @@ -105,7 +105,7 @@ class MagnificationConnectionImpl extends IMagnificationConnection.Stub { } @Override - public void setConnectionCallback(IWindowMagnificationConnectionCallback callback) { + public void setConnectionCallback(IMagnificationConnectionCallback callback) { mConnectionCallback = callback; } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt new file mode 100644 index 000000000000..6483ae44d5ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt @@ -0,0 +1,73 @@ +/* + * 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.accessibility.data.repository + +import android.os.UserHandle +import android.provider.Settings.Secure +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +/** Provides data related to color correction. */ +interface ColorCorrectionRepository { + /** Observable for whether color correction is enabled */ + fun isEnabled(userHandle: UserHandle): Flow<Boolean> + + /** Sets color correction enabled state. */ + suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean +} + +@SysUISingleton +class ColorCorrectionRepositoryImpl +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + private val secureSettings: SecureSettings, +) : ColorCorrectionRepository { + + companion object { + const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED + const val DISABLED = 0 + const val ENABLED = 1 + } + + override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + + override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = + withContext(bgCoroutineContext) { + secureSettings.putIntForUser( + SETTING_NAME, + if (isEnabled) ENABLED else DISABLED, + userHandle.identifier + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt new file mode 100644 index 000000000000..df7fdb8e6058 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt @@ -0,0 +1,124 @@ +/* + * 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.accessibility.qs + +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.ColorCorrectionTile +import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.DreamTile +import com.android.systemui.qs.tiles.FontScalingTile +import com.android.systemui.qs.tiles.NightDisplayTile +import com.android.systemui.qs.tiles.OneHandedModeTile +import com.android.systemui.qs.tiles.ReduceBrightColorsTile +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.ColorCorrectionTileMapper +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.res.R +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface QSAccessibilityModule { + + /** Inject ColorInversionTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ColorInversionTile.TILE_SPEC) + fun bindColorInversionTile(colorInversionTile: ColorInversionTile): QSTileImpl<*> + + /** Inject NightDisplayTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(NightDisplayTile.TILE_SPEC) + fun bindNightDisplayTile(nightDisplayTile: NightDisplayTile): QSTileImpl<*> + + /** Inject ReduceBrightColorsTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ReduceBrightColorsTile.TILE_SPEC) + fun bindReduceBrightColorsTile(reduceBrightColorsTile: ReduceBrightColorsTile): QSTileImpl<*> + + /** Inject OneHandedModeTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(OneHandedModeTile.TILE_SPEC) + fun bindOneHandedModeTile(oneHandedModeTile: OneHandedModeTile): QSTileImpl<*> + + /** Inject ColorCorrectionTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(ColorCorrectionTile.TILE_SPEC) + fun bindColorCorrectionTile(colorCorrectionTile: ColorCorrectionTile): QSTileImpl<*> + + /** Inject DreamTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(DreamTile.TILE_SPEC) + fun bindDreamTile(dreamTile: DreamTile): QSTileImpl<*> + + /** Inject FontScalingTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(FontScalingTile.TILE_SPEC) + fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*> + + companion object { + + const val COLOR_CORRECTION_TILE_SPEC = "color_correction" + + @Provides + @IntoMap + @StringKey(COLOR_CORRECTION_TILE_SPEC) + fun provideColorCorrectionTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(COLOR_CORRECTION_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.ic_qs_color_correction, + labelRes = R.string.quick_settings_color_correction_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject ColorCorrectionTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(COLOR_CORRECTION_TILE_SPEC) + fun provideColorCorrectionTileViewModel( + factory: QSTileViewModelFactory.Static<ColorCorrectionTileModel>, + mapper: ColorCorrectionTileMapper, + stateInteractor: ColorCorrectionTileDataInteractor, + userActionInteractor: ColorCorrectionUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(COLOR_CORRECTION_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index bd84b28ab4d7..fda23b7f2a9c 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -24,9 +24,9 @@ import android.os.UserHandle import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationResultModel -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -80,13 +80,14 @@ interface AuthenticationRepository { val isPatternVisible: StateFlow<Boolean> /** - * The current authentication throttling state, set when the user has to wait before being able - * to try another authentication attempt. `null` indicates throttling isn't active. + * The current authentication lockout (aka "throttling") state, set when the user has to wait + * before being able to try another authentication attempt. `null` indicates throttling isn't + * active. */ - val throttling: MutableStateFlow<AuthenticationThrottlingModel?> + val lockout: MutableStateFlow<AuthenticationLockoutModel?> /** Whether throttling has occurred at least once since the last successful authentication. */ - val hasThrottlingOccurred: MutableStateFlow<Boolean> + val hasLockoutOccurred: MutableStateFlow<Boolean> /** * Whether the auto confirm feature is enabled for the currently-selected user. @@ -138,22 +139,25 @@ interface AuthenticationRepository { /** Reports an authentication attempt. */ suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) + /** Reports that the user has entered a temporary device lockout (throttling). */ + suspend fun reportLockoutStarted(durationMs: Int) + /** Returns the current number of failed authentication attempts. */ suspend fun getFailedAuthenticationAttemptCount(): Int /** - * Returns the timestamp for when the current throttling will end, allowing the user to attempt + * Returns the timestamp for when the current lockout will end, allowing the user to attempt * authentication again. * * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime]. */ - suspend fun getThrottlingEndTimestamp(): Long + suspend fun getLockoutEndTimestamp(): Long /** - * Sets the throttling timeout duration (time during which the user should not be allowed to + * Sets the lockout timeout duration (time during which the user should not be allowed to * attempt authentication). */ - suspend fun setThrottleDuration(durationMs: Int) + suspend fun setLockoutDuration(durationMs: Int) /** * Checks the given [LockscreenCredential] to see if it's correct, returning an @@ -185,10 +189,9 @@ constructor( getFreshValue = lockPatternUtils::isVisiblePatternEnabled, ) - override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> = - MutableStateFlow(null) + override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null) - override val hasThrottlingOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false) override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = refreshingFlow( @@ -252,19 +255,25 @@ constructor( } } + override suspend fun reportLockoutStarted(durationMs: Int) { + return withContext(backgroundDispatcher) { + lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId) + } + } + override suspend fun getFailedAuthenticationAttemptCount(): Int { return withContext(backgroundDispatcher) { lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId) } } - override suspend fun getThrottlingEndTimestamp(): Long { + override suspend fun getLockoutEndTimestamp(): Long { return withContext(backgroundDispatcher) { lockPatternUtils.getLockoutAttemptDeadline(selectedUserId) } } - override suspend fun setThrottleDuration(durationMs: Int) { + override suspend fun setLockoutDuration(durationMs: Int) { withContext(backgroundDispatcher) { lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs) } @@ -276,9 +285,9 @@ constructor( return withContext(backgroundDispatcher) { try { val matched = lockPatternUtils.checkCredential(credential, selectedUserId) {} - AuthenticationResultModel(isSuccessful = matched, throttleDurationMs = 0) + AuthenticationResultModel(isSuccessful = matched, lockoutDurationMs = 0) } catch (ex: LockPatternUtils.RequestThrottledException) { - AuthenticationResultModel(isSuccessful = false, throttleDurationMs = ex.timeoutMs) + AuthenticationResultModel(isSuccessful = false, lockoutDurationMs = ex.timeoutMs) } } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 7f8f8875ae98..797154e85082 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -20,9 +20,9 @@ import com.android.app.tracing.TraceUtils.Companion.withContext import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.systemui.authentication.data.repository.AuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -85,10 +85,11 @@ constructor( val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod /** - * The current authentication throttling state, set when the user has to wait before being able - * to try another authentication attempt. `null` indicates throttling isn't active. + * The current authentication lockout (aka "throttling") state, set when the user has to wait + * before being able to try another authentication attempt. `null` indicates lockout isn't + * active. */ - val throttling: StateFlow<AuthenticationThrottlingModel?> = repository.throttling + val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout /** * Whether the auto confirm feature is enabled for the currently-selected user. @@ -97,12 +98,12 @@ constructor( * [hintedPinLength]. */ val isAutoConfirmEnabled: StateFlow<Boolean> = - combine(repository.isAutoConfirmFeatureEnabled, repository.hasThrottlingOccurred) { + combine(repository.isAutoConfirmFeatureEnabled, repository.hasLockoutOccurred) { featureEnabled, - hasThrottlingOccurred -> - // Disable auto-confirm if throttling occurred since the last successful + hasLockoutOccurred -> + // Disable auto-confirm if lockout occurred since the last successful // authentication attempt. - featureEnabled && !hasThrottlingOccurred + featureEnabled && !hasLockoutOccurred } .stateIn( scope = applicationScope, @@ -139,7 +140,7 @@ constructor( /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled - private var throttlingCountdownJob: Job? = null + private var lockoutCountdownJob: Job? = null init { applicationScope.launch { @@ -188,8 +189,8 @@ constructor( val authMethod = getAuthenticationMethod() val skipCheck = when { - // Throttling is active, the UI layer should not have called this; skip the attempt. - throttling.value != null -> true + // Lockout is active, the UI layer should not have called this; skip the attempt. + lockout.value != null -> true // The input is too short; skip the attempt. input.isTooShort(authMethod) -> true // Auto-confirm attempt when the feature is not enabled; skip the attempt. @@ -215,21 +216,22 @@ constructor( ) } - // Check if we need to throttle and, if so, kick off the throttle countdown: - if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) { - repository.setThrottleDuration( - durationMs = authenticationResult.throttleDurationMs, - ) - repository.hasThrottlingOccurred.value = true - startThrottlingCountdown() + // Check if lockout should start and, if so, kick off the countdown: + if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) { + repository.apply { + setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs) + reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs) + hasLockoutOccurred.value = true + } + startLockoutCountdown() } if (authenticationResult.isSuccessful) { - // Since authentication succeeded, we should refresh throttling to make sure that our - // state is completely reflecting the upstream source of truth. - refreshThrottling() + // Since authentication succeeded, refresh lockout to make sure the state is completely + // reflecting the upstream source of truth. + refreshLockout() - repository.hasThrottlingOccurred.value = false + repository.hasLockoutOccurred.value = false } return if (authenticationResult.isSuccessful) { @@ -247,52 +249,52 @@ constructor( } } - /** Starts refreshing the throttling state every second. */ - private suspend fun startThrottlingCountdown() { - cancelThrottlingCountdown() - throttlingCountdownJob = + /** Starts refreshing the lockout state every second. */ + private suspend fun startLockoutCountdown() { + cancelLockoutCountdown() + lockoutCountdownJob = applicationScope.launch { - while (refreshThrottling()) { + while (refreshLockout()) { delay(1.seconds.inWholeMilliseconds) } } } - /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */ - private fun cancelThrottlingCountdown() { - throttlingCountdownJob?.cancel() - throttlingCountdownJob = null + /** Cancels any lockout state countdown started in [startLockoutCountdown]. */ + private fun cancelLockoutCountdown() { + lockoutCountdownJob?.cancel() + lockoutCountdownJob = null } /** Notifies that the currently-selected user has changed. */ private suspend fun onSelectedUserChanged() { - cancelThrottlingCountdown() - if (refreshThrottling()) { - startThrottlingCountdown() + cancelLockoutCountdown() + if (refreshLockout()) { + startLockoutCountdown() } } /** - * Refreshes the throttling state, hydrating the repository with the latest state. + * Refreshes the lockout state, hydrating the repository with the latest state. * - * @return Whether throttling is active or not. + * @return Whether lockout is active or not. */ - private suspend fun refreshThrottling(): Boolean { - withContext("$TAG#refreshThrottling", backgroundDispatcher) { + private suspend fun refreshLockout(): Boolean { + withContext("$TAG#refreshLockout", backgroundDispatcher) { val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() } - val deadline = async { repository.getThrottlingEndTimestamp() } + val deadline = async { repository.getLockoutEndTimestamp() } val remainingMs = max(0, deadline.await() - clock.elapsedRealtime()) - repository.throttling.value = + repository.lockout.value = if (remainingMs > 0) { - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = failedAttemptCount.await(), remainingSeconds = ceil(remainingMs / 1000f).toInt(), ) } else { - null // Throttling ended. + null // Lockout ended. } } - return repository.throttling.value != null + return repository.lockout.value != null } private fun AuthenticationMethodModel.createCredential( diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt index 8392528b86aa..8ee2d5e02bad 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt @@ -16,17 +16,17 @@ package com.android.systemui.authentication.shared.model -/** Models a state for throttling the next authentication attempt. */ -data class AuthenticationThrottlingModel( +/** Models a state for temporarily locking out the next authentication attempt. */ +data class AuthenticationLockoutModel( - /** Number of failed authentication attempts so far. If not throttling this will be `0`. */ + /** Number of failed authentication attempts so far. If not locked out this will be `0`. */ val failedAttemptCount: Int = 0, /** * Remaining amount of time, in seconds, before another authentication attempt can be done. If - * not throttling this will be `0`. + * not locked out this will be `0`. * - * This number is changed throughout the timeout. + * This number is changed throughout the lockout. * * Note: this isn't precise (in milliseconds), but rounded up to ensure "at most" this amount of * seconds remains. diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt index f2a3e74700db..addc75e52fad 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt @@ -21,5 +21,5 @@ data class AuthenticationResultModel( /** Whether authentication was successful. */ val isSuccessful: Boolean = false, /** If [isSuccessful] is `false`, how long the user must wait before trying again. */ - val throttleDurationMs: Int = 0, + val lockoutDurationMs: Int = 0, ) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index a2ac66f6d831..63fe26a37e46 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -197,11 +197,42 @@ open class UdfpsKeyguardViewControllerLegacy( listenForGoneToAodTransition(this) listenForLockscreenAodTransitions(this) listenForAodToOccludedTransitions(this) + listenForAlternateBouncerToAodTransitions(this) + listenForDreamingToAodTransitions(this) } } } @VisibleForTesting + suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job { + return scope.launch { + transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect { + transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + ANIMATE_APPEAR_ON_SCREEN_OFF, + ) + } + } + } + + @VisibleForTesting + suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job { + return scope.launch { + transitionInteractor + .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD) + .collect { transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, + ) + } + } + } + + @VisibleForTesting suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job { return scope.launch { transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect { @@ -246,7 +277,10 @@ open class UdfpsKeyguardViewControllerLegacy( suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job { return scope.launch { transitionInteractor.dozeAmountTransition.collect { transitionStep -> - if (transitionStep.transitionState == TransitionState.CANCELED) { + if ( + transitionStep.from == KeyguardState.AOD && + transitionStep.transitionState == TransitionState.CANCELED + ) { if ( transitionInteractor.startedKeyguardTransitionStep.first().to != KeyguardState.AOD diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 32d9067bd9e4..90e4a3821634 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -376,6 +376,22 @@ object BiometricViewBinder { } } + // Talkback directional guidance + backgroundView.setOnHoverListener { _, event -> + launch { + viewModel.onAnnounceAccessibilityHint( + event, + accessibilityManager.isTouchExplorationEnabled + ) + } + false + } + launch { + viewModel.accessibilityHint.collect { message -> + if (message.isNotBlank()) view.announceForAccessibility(message) + } + } + // Play haptics launch { viewModel.hapticsToPlay.collect { hapticFeedbackConstant -> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 647aaf392ed8..6d0a58e202bd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -21,9 +21,12 @@ import android.hardware.biometrics.BiometricPrompt import android.util.Log import android.view.HapticFeedbackConstants import android.view.MotionEvent +import com.android.systemui.Flags.bpTalkback +import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.DisplayRotation @@ -35,7 +38,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +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 @@ -49,7 +54,9 @@ class PromptViewModel constructor( displayStateInteractor: DisplayStateInteractor, promptSelectorInteractor: PromptSelectorInteractor, - @Application context: Context, + @Application private val context: Context, + private val udfpsOverlayInteractor: UdfpsOverlayInteractor, + private val udfpsUtils: UdfpsUtils ) { /** The set of modalities available for this prompt */ val modalities: Flow<BiometricModalities> = @@ -69,6 +76,11 @@ constructor( val faceIconHeight: Int = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) + private val _accessibilityHint = MutableSharedFlow<String>() + + /** Hint for talkback directional guidance */ + val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow() + private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false) /** If the user is currently authenticating (i.e. at least one biometric is scanning). */ @@ -516,6 +528,40 @@ constructor( return false } + /** Sets the message used for UDFPS directional guidance */ + suspend fun onAnnounceAccessibilityHint( + event: MotionEvent, + touchExplorationEnabled: Boolean, + ): Boolean { + if (bpTalkback() && modalities.first().hasUdfps && touchExplorationEnabled) { + // TODO(b/315184924): Remove uses of UdfpsUtils + val scaledTouch = + udfpsUtils.getTouchInNativeCoordinates( + event.getPointerId(0), + event, + udfpsOverlayInteractor.udfpsOverlayParams.value + ) + if ( + !udfpsUtils.isWithinSensorArea( + event.getPointerId(0), + event, + udfpsOverlayInteractor.udfpsOverlayParams.value + ) + ) { + _accessibilityHint.emit( + udfpsUtils.onTouchOutsideOfSensorArea( + touchExplorationEnabled, + context, + scaledTouch.x, + scaledTouch.y, + udfpsOverlayInteractor.udfpsOverlayParams.value + ) + ) + } + } + return false + } + /** * Switch to the credential view. * diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 677f60d405b8..724c0fe1e4e4 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.domain.interactor import android.content.Context import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.classifier.FalsingClassifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor @@ -60,24 +60,25 @@ constructor( /** The user-facing message to show in the bouncer. */ val message: StateFlow<String?> = - combine(repository.message, authenticationInteractor.throttling) { message, throttling -> - messageOrThrottlingMessage(message, throttling) + combine(repository.message, authenticationInteractor.lockout) { message, lockout -> + messageOrLockoutMessage(message, lockout) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = - messageOrThrottlingMessage( + messageOrLockoutMessage( repository.message.value, - authenticationInteractor.throttling.value, + authenticationInteractor.lockout.value, ) ) /** - * The current authentication throttling state, set when the user has to wait before being able - * to try another authentication attempt. `null` indicates throttling isn't active. + * The current authentication lockout (aka "throttling") state, set when the user has to wait + * before being able to try another authentication attempt. `null` indicates lockout isn't + * active. */ - val throttling: StateFlow<AuthenticationThrottlingModel?> = authenticationInteractor.throttling + val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout /** Whether the auto confirm feature is enabled for the currently-selected user. */ val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled @@ -102,9 +103,9 @@ constructor( init { if (flags.isEnabled()) { - // Clear the message if moved from throttling to no-longer throttling. + // Clear the message if moved from locked-out to no-longer locked-out. applicationScope.launch { - throttling.pairwise().collect { (previous, current) -> + lockout.pairwise().collect { (previous, current) -> if (previous != null && current == null) { clearMessage() } @@ -213,9 +214,9 @@ constructor( * Shows the error message. * * Callers should use this instead of [authenticate] when they know ahead of time that an auth - * attempt will fail but aren't interested in the other side effects like triggering throttling. + * attempt will fail but aren't interested in the other side effects like triggering lockout. * For example, if the user entered a pattern that's too short, the system can show the error - * message without having the attempt trigger throttling. + * message without having the attempt trigger lockout. */ private suspend fun showErrorMessage() { repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod())) @@ -250,15 +251,15 @@ constructor( } } - private fun messageOrThrottlingMessage( + private fun messageOrLockoutMessage( message: String?, - throttlingModel: AuthenticationThrottlingModel?, + lockoutModel: AuthenticationLockoutModel?, ): String { return when { - throttlingModel != null -> + lockoutModel != null -> applicationContext.getString( com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown, - throttlingModel.remainingSeconds, + lockoutModel.remainingSeconds, ) message != null -> message else -> "" diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt index 5385442092b9..7f97718cb623 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt @@ -21,13 +21,13 @@ import androidx.annotation.VisibleForTesting /** Enumerates all known adaptive layout configurations. */ enum class BouncerSceneLayout { /** The default UI with the bouncer laid out normally. */ - STANDARD, + STANDARD_BOUNCER, /** The bouncer is displayed vertically stacked with the user switcher. */ - STACKED, + BELOW_USER_SWITCHER, /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ - SIDE_BY_SIDE, + BESIDE_USER_SWITCHER, /** The bouncer is split in two with both sides shown side-by-side. */ - SPLIT, + SPLIT_BOUNCER, } /** Enumerates the supported window size classes. */ @@ -48,19 +48,19 @@ fun calculateLayoutInternal( isSideBySideSupported: Boolean, ): BouncerSceneLayout { return when (height) { - SizeClass.COMPACT -> BouncerSceneLayout.SPLIT + SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER SizeClass.MEDIUM -> when (width) { - SizeClass.COMPACT -> BouncerSceneLayout.STANDARD - SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD - SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER } SizeClass.EXPANDED -> when (width) { - SizeClass.COMPACT -> BouncerSceneLayout.STANDARD - SizeClass.MEDIUM -> BouncerSceneLayout.STACKED - SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER + SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER } - }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported } - ?: BouncerSceneLayout.STANDARD + }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isSideBySideSupported } + ?: BouncerSceneLayout.STANDARD_BOUNCER } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index e379dab918ef..0d7f6dcce1c7 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -50,12 +50,12 @@ sealed class AuthMethodBouncerViewModel( abstract val authenticationMethod: AuthenticationMethodModel /** - * String resource ID of the failure message to be shown during throttling. + * String resource ID of the failure message to be shown during lockout. * * The message must include 2 number parameters: the first one indicating how many unsuccessful - * attempts were made, and the second one indicating in how many seconds throttling will expire. + * attempts were made, and the second one indicating in how many seconds lockout will expire. */ - @get:StringRes abstract val throttlingMessageId: Int + @get:StringRes abstract val lockoutMessageId: Int /** Notifies that the UI has been shown to the user. */ fun onShown() { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index fefc3e3411dd..4b1434323886 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -105,17 +105,17 @@ class BouncerViewModel( get() = bouncerInteractor.isUserSwitcherVisible private val isInputEnabled: StateFlow<Boolean> = - bouncerInteractor.throttling + bouncerInteractor.lockout .map { it == null } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = bouncerInteractor.throttling.value == null, + initialValue = bouncerInteractor.lockout.value == null, ) // Handle to the scope of the child ViewModel (stored in [authMethod]). private var childViewModelScope: CoroutineScope? = null - private val _throttlingDialogMessage = MutableStateFlow<String?>(null) + private val _dialogMessage = MutableStateFlow<String?>(null) /** View-model for the current UI, based on the current authentication method. */ val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> = @@ -128,20 +128,20 @@ class BouncerViewModel( ) /** - * A message for a throttling dialog to show when the user has attempted the wrong credential - * too many times and now must wait a while before attempting again. + * A message for a dialog to show when the user has attempted the wrong credential too many + * times and now must wait a while before attempting again. * * If `null`, no dialog should be shown. * - * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user - * dismisses this dialog. + * Once the dialog is shown, the UI should call [onDialogDismissed] when the user dismisses this + * dialog. */ - val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow() + val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow() /** The user-facing message to show in the bouncer. */ val message: StateFlow<MessageViewModel> = - combine(bouncerInteractor.message, bouncerInteractor.throttling) { message, throttling -> - toMessageViewModel(message, isThrottled = throttling != null) + combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout -> + toMessageViewModel(message, isLockedOut = lockout != null) } .stateIn( scope = applicationScope, @@ -149,7 +149,7 @@ class BouncerViewModel( initialValue = toMessageViewModel( message = bouncerInteractor.message.value, - isThrottled = bouncerInteractor.throttling.value != null, + isLockedOut = bouncerInteractor.lockout.value != null, ), ) @@ -197,28 +197,28 @@ class BouncerViewModel( init { if (flags.isEnabled()) { applicationScope.launch { - combine(bouncerInteractor.throttling, authMethodViewModel) { - throttling, + combine(bouncerInteractor.lockout, authMethodViewModel) { + lockout, authMethodViewModel -> - if (throttling != null && authMethodViewModel != null) { + if (lockout != null && authMethodViewModel != null) { applicationContext.getString( - authMethodViewModel.throttlingMessageId, - throttling.failedAttemptCount, - throttling.remainingSeconds, + authMethodViewModel.lockoutMessageId, + lockout.failedAttemptCount, + lockout.remainingSeconds, ) } else { null } } .distinctUntilChanged() - .collect { dialogMessage -> _throttlingDialogMessage.value = dialogMessage } + .collect { dialogMessage -> _dialogMessage.value = dialogMessage } } } } - /** Notifies that a throttling dialog has been dismissed by the user. */ - fun onThrottlingDialogDismissed() { - _throttlingDialogMessage.value = null + /** Notifies that the dialog has been dismissed by the user. */ + fun onDialogDismissed() { + _dialogMessage.value = null } private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean { @@ -231,11 +231,11 @@ class BouncerViewModel( private fun toMessageViewModel( message: String?, - isThrottled: Boolean, + isLockedOut: Boolean, ): MessageViewModel { return MessageViewModel( text = message ?: "", - isUpdateAnimated = !isThrottled, + isUpdateAnimated = !isLockedOut, ) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 3b7e32140560..b68271767fc2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -46,7 +46,7 @@ class PasswordBouncerViewModel( override val authenticationMethod = AuthenticationMethodModel.Password - override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message + override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message /** Whether the input method editor (for example, the software keyboard) is visible. */ private var isImeVisible: Boolean = false @@ -56,13 +56,13 @@ class PasswordBouncerViewModel( /** Whether the UI should request focus on the text field element. */ val isTextFieldFocusRequested = - combine(interactor.throttling, isTextFieldFocused) { throttling, hasFocus -> + combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus -> throttling == null && !hasFocus } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(), - initialValue = interactor.throttling.value == null && !isTextFieldFocused.value, + initialValue = interactor.lockout.value == null && !isTextFieldFocused.value, ) override fun onHidden() { @@ -104,7 +104,7 @@ class PasswordBouncerViewModel( * hidden. */ suspend fun onImeVisibilityChanged(isVisible: Boolean) { - if (isImeVisible && !isVisible && interactor.throttling.value == null) { + if (isImeVisible && !isVisible && interactor.lockout.value == null) { interactor.onImeHiddenByUser() } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index b1c5ab6122fe..69f8032ef4f2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -80,7 +80,7 @@ class PatternBouncerViewModel( override val authenticationMethod = AuthenticationMethodModel.Pattern - override val throttlingMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message + override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message /** Notifies that the user has started a drag gesture across the dot grid. */ fun onDragStart() { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index e25e82fe04c3..7f4a0296ebdc 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -104,7 +104,7 @@ class PinBouncerViewModel( override val authenticationMethod: AuthenticationMethodModel = authenticationMethod - override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message + override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message init { viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } } diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt index fdd98bec0a2d..3063ebd60b0c 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt @@ -18,8 +18,12 @@ package com.android.systemui.common.shared.model /** Models the bounds of the notification container. */ data class NotificationContainerBounds( + /** The position of the left of the container in its window coordinate system, in pixels. */ + val left: Float = 0f, /** The position of the top of the container in its window coordinate system, in pixels. */ val top: Float = 0f, + /** The position of the right of the container in its window coordinate system, in pixels. */ + val right: Float = 0f, /** The position of the bottom of the container in its window coordinate system, in pixels. */ val bottom: Float = 0f, /** Whether any modifications to top/bottom should be smoothly animated. */ 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..e342c6bca6fa 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]. @@ -108,7 +122,7 @@ constructor( if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) { flowOf(emptyList()) } else { - smartspaceRepository.lockscreenSmartspaceTargets.map { targets -> + smartspaceRepository.communalSmartspaceTargets.map { targets -> targets .filter { target -> target.featureType == SmartspaceTarget.FEATURE_TIMER && 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/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt new file mode 100644 index 000000000000..c5610c877f57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt @@ -0,0 +1,195 @@ +/* + * 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.smartspace + +import android.app.smartspace.SmartspaceConfig +import android.app.smartspace.SmartspaceManager +import android.app.smartspace.SmartspaceSession +import android.app.smartspace.SmartspaceTarget +import android.content.Context +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView +import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB +import com.android.systemui.smartspace.SmartspacePrecondition +import com.android.systemui.smartspace.SmartspaceTargetFilter +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER +import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN +import com.android.systemui.util.concurrency.Execution +import java.util.Optional +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Named + +/** Controller for managing the smartspace view on the dream */ +@SysUISingleton +class CommunalSmartspaceController +@Inject +constructor( + private val context: Context, + private val smartspaceManager: SmartspaceManager?, + private val execution: Execution, + @Main private val uiExecutor: Executor, + @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition, + @Named(DREAM_SMARTSPACE_TARGET_FILTER) + private val optionalTargetFilter: Optional<SmartspaceTargetFilter>, + @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>, +) { + companion object { + private const val TAG = "CommunalSmartspaceCtrlr" + } + + private var session: SmartspaceSession? = null + private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) + private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null) + + // A shadow copy of listeners is maintained to track whether the session should remain open. + private var listeners = mutableSetOf<SmartspaceTargetListener>() + + private var unfilteredListeners = mutableSetOf<SmartspaceTargetListener>() + + // Smartspace can be used on multiple displays, such as when the user casts their screen + private var smartspaceViews = mutableSetOf<SmartspaceView>() + + var preconditionListener = + object : SmartspacePrecondition.Listener { + override fun onCriteriaChanged() { + reloadSmartspace() + } + } + + init { + precondition.addListener(preconditionListener) + } + + var filterListener = + object : SmartspaceTargetFilter.Listener { + override fun onCriteriaChanged() { + reloadSmartspace() + } + } + + init { + targetFilter?.addListener(filterListener) + } + + private val sessionListener = + SmartspaceSession.OnTargetsAvailableListener { targets -> + execution.assertIsMainThread() + + val filteredTargets = + targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true } + plugin?.onTargetsAvailable(filteredTargets) + } + + private fun hasActiveSessionListeners(): Boolean { + return smartspaceViews.isNotEmpty() || + listeners.isNotEmpty() || + unfilteredListeners.isNotEmpty() + } + + private fun connectSession() { + if (smartspaceManager == null) { + return + } + if (plugin == null) { + return + } + if (session != null || !hasActiveSessionListeners()) { + return + } + + if (!precondition.conditionsMet()) { + return + } + + val newSession = + smartspaceManager.createSmartspaceSession( + SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build() + ) + Log.d(TAG, "Starting smartspace session for dream") + newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) + this.session = newSession + + plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) } + + reloadSmartspace() + } + + /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */ + private fun disconnect() { + if (hasActiveSessionListeners()) return + + execution.assertIsMainThread() + + if (session == null) { + return + } + + session?.let { + it.removeOnTargetsAvailableListener(sessionListener) + it.close() + } + + session = null + + plugin?.registerSmartspaceEventNotifier(null) + plugin?.onTargetsAvailable(emptyList()) + Log.d(TAG, "Ending smartspace session for dream") + } + + fun addListener(listener: SmartspaceTargetListener) { + addAndRegisterListener(listener, plugin) + } + + fun removeListener(listener: SmartspaceTargetListener) { + removeAndUnregisterListener(listener, plugin) + } + + private fun addAndRegisterListener( + listener: SmartspaceTargetListener, + smartspaceDataPlugin: BcSmartspaceDataPlugin? + ) { + execution.assertIsMainThread() + smartspaceDataPlugin?.registerListener(listener) + listeners.add(listener) + + connectSession() + } + + private fun removeAndUnregisterListener( + listener: SmartspaceTargetListener, + smartspaceDataPlugin: BcSmartspaceDataPlugin? + ) { + execution.assertIsMainThread() + smartspaceDataPlugin?.unregisterListener(listener) + listeners.remove(listener) + disconnect() + } + + private fun reloadSmartspace() { + session?.requestSmartspaceUpdate() + } + + private fun onTargetsAvailableUnfiltered(targets: List<SmartspaceTarget>) { + unfilteredListeners.forEach { it.onSmartspaceTargetsUpdated(targets) } + } +} 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/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 7b94fc182fe2..573a748b4290 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -76,6 +76,10 @@ constructor( Intent(applicationContext, WidgetPickerActivity::class.java) ) }, + onEditDone = { + // TODO(b/315154364): in a separate change, lock the device and transition to GH + finish() + } ) } } diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java index 7ac1cc796d4b..9d4ed20ca582 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java @@ -25,11 +25,15 @@ import static com.android.systemui.controls.dagger.ControlsComponent.Visibility. import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; import android.widget.ImageView; +import androidx.annotation.Nullable; + import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.Utils; import com.android.systemui.CoreStartable; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent; @@ -43,6 +47,7 @@ import com.android.systemui.dagger.qualifiers.SystemUser; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.ViewController; import com.android.systemui.util.condition.ConditionalCoreStartable; @@ -195,34 +200,70 @@ public class DreamHomeControlsComplication implements Complication { private final ActivityStarter mActivityStarter; private final Context mContext; + private final ConfigurationController mConfigurationController; private final ControlsComponent mControlsComponent; private final UiEventLogger mUiEventLogger; + private final ConfigurationController.ConfigurationListener mConfigurationListener = + new ConfigurationController.ConfigurationListener() { + @Override + public void onUiModeChanged() { + reloadResources(); + } + }; + @Inject DreamHomeControlsChipViewController( @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, ActivityStarter activityStarter, Context context, + ConfigurationController configurationController, ControlsComponent controlsComponent, UiEventLogger uiEventLogger) { super(view); mActivityStarter = activityStarter; mContext = context; + mConfigurationController = configurationController; mControlsComponent = controlsComponent; mUiEventLogger = uiEventLogger; } @Override protected void onViewAttached() { - mView.setImageResource(mControlsComponent.getTileImageId()); - mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId())); + reloadResources(); mView.setOnClickListener(this::onClickHomeControls); + mConfigurationController.addCallback(mConfigurationListener); } @Override - protected void onViewDetached() {} + protected void onViewDetached() { + mConfigurationController.removeCallback(mConfigurationListener); + } + + private void reloadResources() { + final String title = getControlsTitle(); + if (title != null) { + mView.setContentDescription(title); + } + mView.setImageResource(mControlsComponent.getTileImageId()); + mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + final Drawable background = mView.getBackground(); + if (background != null) { + background.setTintList( + Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface)); + } + } + + @Nullable + private String getControlsTitle() { + try { + return mContext.getString(mControlsComponent.getTileTitleId()); + } catch (Resources.NotFoundException e) { + return null; + } + } private void onClickHomeControls(View v) { if (DEBUG) Log.d(TAG, "home controls complication tapped"); diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java index 08d0595eba23..b6dcfcbfad56 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java @@ -24,9 +24,8 @@ import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.widget.ImageView; -import com.android.settingslib.Utils; -import com.android.systemui.res.R; import com.android.systemui.complication.DreamHomeControlsComplication; +import com.android.systemui.res.R; import com.android.systemui.shared.shadow.DoubleShadowIconDrawable; import com.android.systemui.shared.shadow.DoubleShadowTextHelper; @@ -98,7 +97,7 @@ public interface DreamHomeControlsComplicationComponent { @DreamHomeControlsComplicationScope @Named(DREAM_HOME_CONTROLS_BACKGROUND_DRAWABLE) static Drawable providesHomeControlsBackground(Context context, Resources resources) { - final Drawable background = new DoubleShadowIconDrawable(createShadowInfo( + return new DoubleShadowIconDrawable(createShadowInfo( resources, R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius, R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx, @@ -117,11 +116,6 @@ public interface DreamHomeControlsComplicationComponent { R.dimen.dream_overlay_bottom_affordance_width), resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset) ); - - background.setTintList( - Utils.getColorAttr(context, com.android.internal.R.attr.colorSurface)); - - return background; } private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources, diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 65d44957222a..3a927396527c 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -63,6 +63,7 @@ interface BaseComposeFacade { activity: ComponentActivity, viewModel: BaseCommunalViewModel, onOpenWidgetPicker: () -> Unit, + onEditDone: () -> Unit, ) /** Create a [View] to represent [viewModel] on screen. */ diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt index 63b01edb01fa..0daa058720ba 100644 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt @@ -35,23 +35,22 @@ import java.util.concurrent.Executor import javax.inject.Inject /** Dialog to select contrast options */ -class ContrastDialogDelegate @Inject constructor( - private val sysuiDialogFactory : SystemUIDialog.Factory, +class ContrastDialogDelegate +@Inject +constructor( + private val sysuiDialogFactory: SystemUIDialog.Factory, @Main private val mainExecutor: Executor, private val uiModeManager: UiModeManager, private val userTracker: UserTracker, private val secureSettings: SecureSettings, ) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener { - override fun createDialog(): SystemUIDialog { - return sysuiDialogFactory.create(this) - } - @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout> lateinit var dialogView: View @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD) - override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + override fun createDialog(): SystemUIDialog { + val dialog = sysuiDialogFactory.create(this) dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null) with(dialog) { setView(dialogView) @@ -67,12 +66,16 @@ class ContrastDialogDelegate @Inject constructor( } setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() } } + + return dialog + } + + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { contrastButtons = mapOf( - CONTRAST_LEVEL_STANDARD to dialogView.requireViewById( - R.id.contrast_button_standard), - CONTRAST_LEVEL_MEDIUM to dialogView.requireViewById(R.id.contrast_button_medium), - CONTRAST_LEVEL_HIGH to dialogView.requireViewById(R.id.contrast_button_high) + CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard), + CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium), + CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high) ) contrastButtons.forEach { (contrastLevel, contrastButton) -> diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 0405ca43320f..ca8268dc89a3 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -82,6 +82,7 @@ import com.android.systemui.qs.FgsManagerControllerImpl; import com.android.systemui.qs.QSFragmentStartableModule; import com.android.systemui.qs.footer.dagger.FooterActionsModule; import com.android.systemui.recents.Recents; +import com.android.systemui.recordissue.RecordIssueModule; import com.android.systemui.retail.dagger.RetailModeModule; import com.android.systemui.scene.ui.view.WindowRootViewComponent; import com.android.systemui.screenrecord.ScreenRecordModule; @@ -209,6 +210,7 @@ import javax.inject.Named; PrivacyModule.class, QRCodeScannerModule.class, QSFragmentStartableModule.class, + RecordIssueModule.class, ReferenceModule.class, RetailModeModule.class, ScreenshotModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java index 9c13a8c82b11..3fac865ffb1d 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java @@ -43,7 +43,7 @@ public class DozeAuthRemover implements DozeMachine.Part { if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) { int currentUser = mSelectedUserInteractor.getSelectedUserId(); if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) { - mKeyguardUpdateMonitor.clearBiometricRecognized(); + mKeyguardUpdateMonitor.clearFingerprintRecognized(); } } } 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 5a763b11d6a4..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 ** @@ -437,11 +437,6 @@ object Flags { // 1200 - predictive back @Keep @JvmField - val WM_ENABLE_PREDICTIVE_BACK = - sysPropBooleanFlag("persist.wm.debug.predictive_back", default = true) - - @Keep - @JvmField val WM_ENABLE_PREDICTIVE_BACK_ANIM = sysPropBooleanFlag("persist.wm.debug.predictive_back_anim", default = true) diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt index 7b33e11a0c9c..6cb68bade9a9 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt @@ -42,4 +42,6 @@ data class SliderHapticFeedbackConfig( @FloatRange(from = 0.0, to = 1.0) val upperBookendScale: Float = 1f, /** Vibration scale at the lower bookend of the slider */ @FloatRange(from = 0.0, to = 1.0) val lowerBookendScale: Float = 0.05f, + /** Exponent for power function compensation */ + @FloatRange(from = 0.0, fromInclusive = false) val exponent: Float = 1f / 0.89f, ) diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt index f313fb3eef0f..9e6245ae7f21 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt @@ -21,9 +21,11 @@ import android.os.VibrationEffect import android.view.VelocityTracker import android.view.animation.AccelerateInterpolator import androidx.annotation.FloatRange +import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.VibratorHelper import kotlin.math.abs import kotlin.math.min +import kotlin.math.pow /** * Listener of slider events that triggers haptic feedback. @@ -63,18 +65,29 @@ class SliderHapticFeedbackProvider( * @param[absoluteVelocity] Velocity of the handle when it reached the bookend. */ private fun vibrateOnEdgeCollision(absoluteVelocity: Float) { + val powerScale = scaleOnEdgeCollision(absoluteVelocity) + val vibration = + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale) + .compose() + vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING) + } + + /** + * Get the velocity-based scale at the bookends + * + * @param[absoluteVelocity] Velocity of the handle when it reached the bookend. + * @return The power scale for the vibration. + */ + @VisibleForTesting + fun scaleOnEdgeCollision(absoluteVelocity: Float): Float { val velocityInterpolated = velocityAccelerateInterpolator.getInterpolation( min(absoluteVelocity / config.maxVelocityToScale, 1f) ) val bookendScaleRange = config.upperBookendScale - config.lowerBookendScale val bookendsHitScale = bookendScaleRange * velocityInterpolated + config.lowerBookendScale - - val vibration = - VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, bookendsHitScale) - .compose() - vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING) + return bookendsHitScale.pow(config.exponent) } /** @@ -96,6 +109,31 @@ class SliderHapticFeedbackProvider( val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress) if (deltaProgress < config.deltaProgressForDragThreshold) return + val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress) + + // Trigger the vibration composition + val composition = VibrationEffect.startComposition() + repeat(config.numberOfLowTicks) { + composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, powerScale) + } + vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING) + dragTextureLastTime = currentTime + dragTextureLastProgress = normalizedSliderProgress + } + + /** + * Get the scale of the drag texture vibration. + * + * @param[absoluteVelocity] Absolute velocity of the handle. + * @param[normalizedSliderProgress] Progress of the slider handled normalized to the range from + * 0F to 1F (inclusive). + * @return the scale of the vibration. + */ + @VisibleForTesting + fun scaleOnDragTexture( + absoluteVelocity: Float, + @FloatRange(from = 0.0, to = 1.0) normalizedSliderProgress: Float + ): Float { val velocityInterpolated = velocityAccelerateInterpolator.getInterpolation( min(absoluteVelocity / config.maxVelocityToScale, 1f) @@ -113,15 +151,7 @@ class SliderHapticFeedbackProvider( // Total scale val scale = positionBasedScale + velocityBasedScale - - // Trigger the vibration composition - val composition = VibrationEffect.startComposition() - repeat(config.numberOfLowTicks) { - composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale) - } - vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING) - dragTextureLastTime = currentTime - dragTextureLastProgress = normalizedSliderProgress + return scale.pow(config.exponent) } override fun onHandleAcquiredByTouch() {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 30090874a480..57f3b7071d1d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -141,6 +141,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.dagger.KeyguardModule; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; @@ -1319,6 +1320,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; private SelectedUserInteractor mSelectedUserInteractor; + private KeyguardInteractor mKeyguardInteractor; private final KeyguardStateController mKeyguardStateController; private final KeyguardStateController.Callback mKeyguardStateControllerCallback = @@ -1400,7 +1402,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel, SystemPropertiesHelper systemPropertiesHelper, Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, + KeyguardInteractor keyguardInteractor) { mContext = context; mUserTracker = userTracker; mFalsingCollector = falsingCollector; @@ -1441,6 +1444,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, })); mDozeParameters = dozeParameters; mSelectedUserInteractor = selectedUserInteractor; + mKeyguardInteractor = keyguardInteractor; mStatusBarStateController = statusBarStateController; statusBarStateController.addCallback(this); @@ -2611,14 +2615,15 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } if (mGoingToSleep) { - mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser); + mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser); Log.i(TAG, "Device is going to sleep, aborting keyguardDone"); return; } setPendingLock(false); // user may have authenticated during the screen off animation handleHide(); - mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser); + mKeyguardInteractor.keyguardDoneAnimationsFinished(); + mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 331d892304b3..3925dd1620b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -52,6 +52,7 @@ import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager; import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule; import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule; import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; @@ -154,7 +155,8 @@ public class KeyguardModule { Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel, SystemPropertiesHelper systemPropertiesHelper, Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, + KeyguardInteractor keyguardInteractor) { return new KeyguardViewMediator( context, uiEventLogger, @@ -199,7 +201,8 @@ public class KeyguardModule { dreamingToLockscreenTransitionViewModel, systemPropertiesHelper, wmLockscreenVisibilityManager, - selectedUserInteractor); + selectedUserInteractor, + keyguardInteractor); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index e47c44863980..4d60dd0bea62 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -187,7 +187,8 @@ constructor( faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false private val _isAuthRunning = MutableStateFlow(false) - override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning + override val isAuthRunning: StateFlow<Boolean> + get() = _isAuthRunning private val keyguardSessionId: InstanceId? get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) @@ -253,13 +254,6 @@ constructor( ) .andAllFlows("canFaceAuthRun", faceAuthLog) .flowOn(backgroundDispatcher) - .onEach { - faceAuthLogger.canFaceAuthRunChanged(it) - if (!it) { - // Cancel currently running auth if any of the gating checks are false. - cancel() - } - } .stateIn(applicationScope, SharingStarted.Eagerly, false) // Face detection can run only when lockscreen bypass is enabled @@ -287,12 +281,9 @@ constructor( ) .andAllFlows("canFaceDetectRun", faceDetectLog) .flowOn(backgroundDispatcher) - .onEach { - if (!it) { - cancelDetection() - } - } .stateIn(applicationScope, SharingStarted.Eagerly, false) + observeFaceAuthGatingChecks() + observeFaceDetectGatingChecks() observeFaceAuthResettingConditions() listenForSchedulingWatchdog() processPendingAuthRequests() @@ -313,20 +304,20 @@ constructor( } private fun observeFaceAuthResettingConditions() { - // Clear auth status when keyguard is going away or when the user is switching or device - // starts going to sleep. + // Clear auth status when keyguard done animations finished or when the user is switching + // or device starts going to sleep. merge( powerInteractor.isAsleep, if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE) } else { - keyguardRepository.isKeyguardGoingAway + keyguardRepository.keyguardDoneAnimationsFinished.map { true } }, userRepository.selectedUser.map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS }, ) - .flowOn(backgroundDispatcher) + .flowOn(mainDispatcher) // should revoke auth ASAP in the main thread .onEach { anyOfThemIsTrue -> if (anyOfThemIsTrue) { clearPendingAuthRequest("Resetting auth status") @@ -347,6 +338,17 @@ constructor( pendingAuthenticateRequest.value = null } + private fun observeFaceDetectGatingChecks() { + canRunDetection + .onEach { + if (!it) { + cancelDetection() + } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) + } + private fun isUdfps() = deviceEntryFingerprintAuthRepository.availableFpSensorType.map { it == BiometricType.UNDER_DISPLAY_FINGERPRINT @@ -405,6 +407,20 @@ constructor( ) } + private fun observeFaceAuthGatingChecks() { + canRunFaceAuth + .onEach { + faceAuthLogger.canFaceAuthRunChanged(it) + if (!it) { + // Cancel currently running auth if any of the gating checks are false. + faceAuthLogger.cancellingFaceAuth() + cancel() + } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) + } + private val faceAuthCallback = object : FaceManager.AuthenticationCallback() { override fun onAuthenticationFailed() { @@ -539,7 +555,7 @@ constructor( authenticate(it.uiEvent, it.fallbackToDetection) } } - .flowOn(backgroundDispatcher) + .flowOn(mainDispatcher) .launchIn(applicationScope) } @@ -635,7 +651,6 @@ constructor( override fun cancel() { if (authCancellationSignal == null) return - faceAuthLogger.cancellingFaceAuth() authCancellationSignal?.cancel() cancelNotReceivedHandlerJob?.cancel() cancelNotReceivedHandlerJob = 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..31ef100abbcb 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> @@ -189,6 +192,17 @@ interface KeyguardRepository { /** Observable updated when keyguardDone should be called either now or soon. */ val keyguardDone: Flow<KeyguardDone> + /** + * Emits after the keyguard is done animating away. + * + * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed. + */ + @Deprecated( + "Use KeyguardTransitionInteractor flows instead. The closest match for " + + "'keyguardDoneAnimationsFinished' is when the GONE transition is finished." + ) + val keyguardDoneAnimationsFinished: Flow<Unit> + /** Receive whether clock should be centered on lockscreen. */ val clockShouldBeCentered: Flow<Boolean> @@ -236,6 +250,17 @@ interface KeyguardRepository { suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone) fun setClockShouldBeCentered(shouldBeCentered: Boolean) + + /** + * Updates signal that the keyguard done animations are finished + * + * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed. + */ + @Deprecated( + "Use KeyguardTransitionInteractor flows instead. The closest match for " + + "'keyguardDoneAnimationsFinished' is when the GONE transition is finished." + ) + fun keyguardDoneAnimationsFinished() } /** Encapsulates application state for the keyguard. */ @@ -266,6 +291,11 @@ constructor( _keyguardDone.emit(keyguardDoneType) } + override val keyguardDoneAnimationsFinished: MutableSharedFlow<Unit> = MutableSharedFlow() + override fun keyguardDoneAnimationsFinished() { + keyguardDoneAnimationsFinished.tryEmit(Unit) + } + private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = _animateBottomAreaDozingTransitions.asStateFlow() @@ -423,6 +453,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/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 0b6b971f2314..7fdcf2f09bc1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -48,7 +48,7 @@ constructor( override fun start() { listenForDreamingToOccluded() listenForDreamingToGone() - listenForDreamingToDozing() + listenForDreamingToAodOrDozing() listenForTransitionToCamera(scope, keyguardInteractor) } @@ -94,7 +94,7 @@ constructor( } } - private fun listenForDreamingToDozing() { + private fun listenForDreamingToAodOrDozing() { scope.launch { combine( keyguardInteractor.dozeTransitionModel, @@ -102,11 +102,12 @@ constructor( ::Pair ) .collect { (dozeTransitionModel, keyguardState) -> - if ( - dozeTransitionModel.to == DozeStateModel.DOZE && - keyguardState == KeyguardState.DREAMING - ) { - startTransitionTo(KeyguardState.DOZING) + if (keyguardState == KeyguardState.DREAMING) { + if (dozeTransitionModel.to == DozeStateModel.DOZE) { + startTransitionTo(KeyguardState.DOZING) + } else if (dozeTransitionModel.to == DozeStateModel.DOZE_AOD) { + startTransitionTo(KeyguardState.AOD) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 5ed70b526f1b..046916aeb277 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -78,6 +78,9 @@ interface KeyguardFaceAuthInteractor { * flows. */ interface FaceAuthenticationListener { + /** Receive face isAuthenticated updates */ + fun onAuthenticatedChanged(isAuthenticated: Boolean) + /** Receive face authentication status updates */ fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) 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..21651ba2cc2b 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,14 @@ constructor( repository.lastRootViewTapPosition.value = point } + fun setAmbientIndicationVisible(isVisible: Boolean) { + repository.ambientIndicationVisible.value = isVisible + } + + fun keyguardDoneAnimationsFinished() { + repository.keyguardDoneAnimationsFinished() + } + companion object { private const val TAG = "KeyguardInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 448411edb168..7882a9758105 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -46,6 +46,7 @@ import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAfforda import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController @@ -66,6 +67,7 @@ class KeyguardQuickAffordanceInteractor @Inject constructor( private val keyguardInteractor: KeyguardInteractor, + private val shadeInteractor: ShadeInteractor, private val lockPatternUtils: LockPatternUtils, private val keyguardStateController: KeyguardStateController, private val userTracker: UserTracker, @@ -100,9 +102,10 @@ constructor( quickAffordanceAlwaysVisible(position), keyguardInteractor.isDozing, keyguardInteractor.isKeyguardShowing, + shadeInteractor.anyExpansion, biometricSettingsRepository.isCurrentUserInLockdown, - ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown -> - if (!isDozing && isKeyguardShowing && !isUserInLockdown) { + ) { affordance, isDozing, isKeyguardShowing, qsExpansion, isUserInLockdown -> + if (!isDozing && isKeyguardShowing && (qsExpansion < 1.0f) && !isUserInLockdown) { affordance } else { KeyguardQuickAffordanceModel.Hidden @@ -117,10 +120,14 @@ constructor( * This is useful for experiences like the lock screen preview mode, where the affordances must * always be visible. */ - fun quickAffordanceAlwaysVisible( + suspend fun quickAffordanceAlwaysVisible( position: KeyguardQuickAffordancePosition, ): Flow<KeyguardQuickAffordanceModel> { - return quickAffordanceInternal(position) + return if (isFeatureDisabledByDevicePolicy()) { + flowOf(KeyguardQuickAffordanceModel.Hidden) + } else { + quickAffordanceInternal(position) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 532df4afebf7..e3f47397eca3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -16,8 +16,10 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.trust.TrustManager import android.content.Context import android.hardware.biometrics.BiometricFaceConstants +import android.hardware.biometrics.BiometricSourceType import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.FaceWakeUpTriggersConfig import com.android.keyguard.KeyguardUpdateMonitor @@ -29,7 +31,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository @@ -70,7 +71,6 @@ constructor( private val context: Context, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, - @Background private val backgroundDispatcher: CoroutineDispatcher, private val repository: DeviceEntryFaceAuthRepository, private val primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>, private val alternateBouncerInteractor: AlternateBouncerInteractor, @@ -83,6 +83,7 @@ constructor( private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig, private val powerInteractor: PowerInteractor, private val biometricSettingsRepository: BiometricSettingsRepository, + private val trustManager: TrustManager, ) : CoreStartable, KeyguardFaceAuthInteractor { private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() @@ -106,7 +107,6 @@ constructor( fallbackToDetect = false ) } - .flowOn(backgroundDispatcher) .launchIn(applicationScope) alternateBouncerInteractor.isVisible @@ -118,7 +118,6 @@ constructor( fallbackToDetect = false ) } - .flowOn(backgroundDispatcher) .launchIn(applicationScope) merge( @@ -147,7 +146,6 @@ constructor( fallbackToDetect = true ) } - .flowOn(backgroundDispatcher) .launchIn(applicationScope) deviceEntryFingerprintAuthRepository.isLockedOut @@ -160,7 +158,6 @@ constructor( } } } - .flowOn(backgroundDispatcher) .launchIn(applicationScope) // User switching should stop face auth and then when it is complete we should trigger face @@ -184,7 +181,6 @@ constructor( ) } } - .flowOn(backgroundDispatcher) .launchIn(applicationScope) } @@ -291,6 +287,19 @@ constructor( .onEach { running -> listeners.forEach { it.onRunningStateChanged(running) } } .flowOn(mainDispatcher) .launchIn(applicationScope) + repository.isAuthenticated + .sample(userRepository.selectedUserInfo, ::Pair) + .onEach { (isAuthenticated, userInfo) -> + if (!isAuthenticated) { + faceAuthenticationLogger.clearFaceRecognized() + trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id) + } + } + .onEach { (isAuthenticated, _) -> + listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) biometricSettingsRepository.isFaceAuthEnrolledAndEnabled .onEach { enrolledAndEnabled -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt index acfd3b0bcf57..24240dfe7402 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt @@ -30,6 +30,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants +import com.android.systemui.util.kotlin.logD import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -58,11 +59,14 @@ constructor( observer = PreviewLifecycleObserver( - renderer, applicationScope, mainDispatcher, + renderer, ::destroyObserver, ) + + logD(TAG) { "Created observer $observer" } + // Destroy any previous renderer associated with this token. activePreviews[renderer.id]?.let { destroyObserver(it) } activePreviews[renderer.id] = observer @@ -80,6 +84,8 @@ constructor( observer, ) ) + // NOTE: The process on the other side can retain messenger indefinitely. + // (e.g. GC might not trigger and cleanup the reference) val msg = Message.obtain() msg.replyTo = messenger result.putParcelable(KEY_PREVIEW_CALLBACK, msg) @@ -99,57 +105,84 @@ constructor( } } - private class PreviewLifecycleObserver( - private val renderer: KeyguardPreviewRenderer, - private val scope: CoroutineScope, - private val mainDispatcher: CoroutineDispatcher, - private val requestDestruction: (PreviewLifecycleObserver) -> Unit, - ) : Handler.Callback, IBinder.DeathRecipient { + companion object { + internal const val TAG = "KeyguardRemotePreviewManager" + @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package" + @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback" + } +} - private var isDestroyedOrDestroying = false +/** + * Handles messages from the other process and handles cleanup. + * + * NOTE: The other process might hold on to reference of this class indefinitely. It's entirely + * possible that GC won't trigger and we'll leak this for all times even if [onDestroy] was called. + * This helps make sure no non-Singleton objects are retained beyond destruction to prevent leaks. + */ +@VisibleForTesting(VisibleForTesting.PRIVATE) +class PreviewLifecycleObserver( + private val scope: CoroutineScope, + private val mainDispatcher: CoroutineDispatcher, + renderer: KeyguardPreviewRenderer, + onDestroy: (PreviewLifecycleObserver) -> Unit, +) : Handler.Callback, IBinder.DeathRecipient { + + private var isDestroyedOrDestroying = false + // These two are null after destruction + @VisibleForTesting var renderer: KeyguardPreviewRenderer? + @VisibleForTesting var onDestroy: ((PreviewLifecycleObserver) -> Unit)? + + init { + this.renderer = renderer + this.onDestroy = onDestroy + } - override fun handleMessage(message: Message): Boolean { - if (isDestroyedOrDestroying) { - return true - } + override fun handleMessage(message: Message): Boolean { + if (isDestroyedOrDestroying) { + return true + } - when (message.what) { - KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> { - message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId -> - renderer.onSlotSelected(slotId = slotId) - } + when (message.what) { + KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> { + message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId -> + checkNotNull(renderer).onSlotSelected(slotId = slotId) } - KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> { - renderer.hideSmartspace( + } + KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> { + checkNotNull(renderer) + .hideSmartspace( message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE) ) - } - else -> requestDestruction(this) } - - return true + else -> checkNotNull(onDestroy).invoke(this) } - override fun binderDied() { - requestDestruction(this) + return true + } + + override fun binderDied() { + onDestroy?.invoke(this) + } + + fun onDestroy(): Pair<IBinder?, Int>? { + if (isDestroyedOrDestroying) { + return null } - fun onDestroy(): Pair<IBinder?, Int>? { - if (isDestroyedOrDestroying) { - return null - } + logD(TAG) { "Destroying $this" } - isDestroyedOrDestroying = true - val hostToken = renderer.hostToken + isDestroyedOrDestroying = true + return renderer?.let { rendererToDestroy -> + this.renderer = null + this.onDestroy = null + val hostToken = rendererToDestroy.hostToken hostToken?.unlinkToDeath(this, 0) - scope.launch(mainDispatcher) { renderer.destroy() } - return renderer.id + scope.launch(mainDispatcher) { rendererToDestroy.destroy() } + rendererToDestroy.id } } companion object { private const val TAG = "KeyguardRemotePreviewManager" - @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package" - @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback" } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index a64a422a1924..e7b6e44450bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -26,7 +26,6 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R @@ -92,13 +91,7 @@ constructor( connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) - val lockId = - if (DeviceEntryUdfpsRefactor.isEnabled) { - R.id.device_entry_icon_view - } else { - R.id.lock_icon_view - } - connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP) + addNotificationPlaceholderBarrier(this) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index a25471cba66d..400d0dc2b242 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -20,7 +20,12 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context import android.view.View import android.view.ViewGroup +import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R @@ -54,6 +59,29 @@ constructor( private val placeHolderId = R.id.nssl_placeholder private var disposableHandle: DisposableHandle? = null + /** + * Align the notification placeholder bottom to the top of either the lock icon or the ambient + * indication area, whichever is higher. + */ + protected fun addNotificationPlaceholderBarrier(constraintSet: ConstraintSet) { + val lockId = + if (DeviceEntryUdfpsRefactor.isEnabled) { + R.id.device_entry_icon_view + } else { + R.id.lock_icon_view + } + + constraintSet.apply { + createBarrier( + R.id.nssl_placeholder_barrier_bottom, + Barrier.TOP, + 0, + *intArrayOf(lockId, R.id.ambient_indication_container) + ) + connect(R.id.nssl_placeholder, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP) + } + } + override fun addViews(constraintLayout: ConstraintLayout) { if (!KeyguardShadeMigrationNssl.isEnabled) { return @@ -85,9 +113,9 @@ constructor( ) if (sceneContainerFlags.flexiNotifsEnabled()) { NotificationStackAppearanceViewBinder.bind( + context, sharedNotificationContainer, notificationStackAppearanceViewModel, - sceneContainerFlags, ambientState, controller, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index f5963be55b2d..b0b5c81dd11c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -19,14 +19,12 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R @@ -97,13 +95,7 @@ constructor( connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) - val lockId = - if (DeviceEntryUdfpsRefactor.isEnabled) { - R.id.device_entry_icon_view - } else { - R.id.lock_icon_view - } - connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP) + addNotificationPlaceholderBarrier(this) } } } 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 4588e02df10e..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 @@ -111,10 +111,21 @@ constructor( /** An observable for the alpha level for the entire keyguard root view. */ val alpha: Flow<Float> = - merge( - keyguardInteractor.keyguardAlpha.distinctUntilChanged(), - occludedToLockscreenTransitionViewModel.lockscreenAlpha, - ) + combine( + keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) }, + merge( + keyguardInteractor.keyguardAlpha, + occludedToLockscreenTransitionViewModel.lockscreenAlpha, + ) + ) { transitionToGone, alpha -> + if (transitionToGone == 1f) { + // Ensures content is not visible when in GONE state + 0f + } else { + alpha + } + } + .distinctUntilChanged() private fun burnIn(): Flow<BurnInModel> { val dozingAmount: Flow<Float> = @@ -169,7 +180,9 @@ constructor( goneToAodTransitionViewModel .enterFromTopTranslationY(enterFromTopAmount) .onStart { emit(0f) }, - occludedToLockscreenTransitionViewModel.lockscreenTranslationY, + occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart { + emit(0f) + }, ) { keyguardTransitionY, burnInTranslationY, @@ -182,6 +195,7 @@ constructor( occludedToLockscreenTransitionTranslationY } } + .distinctUntilChanged() val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() } @@ -229,7 +243,9 @@ constructor( .distinctUntilChanged() fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) { - keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom)) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = top, bottom = bottom) + ) } /** Is there an expanded pulse, are we animating in response? */ diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index 8c5690b312ec..3c2facbc967a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -132,6 +132,10 @@ constructor( logBuffer.log(TAG, DEBUG, "Face authentication failed") } + fun clearFaceRecognized() { + logBuffer.log(TAG, DEBUG, "Clear face recognized") + } + fun authenticationError( errorCode: Int, errString: CharSequence?, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt index 14d4b6800b0a..c87fd14a943d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt @@ -119,7 +119,7 @@ internal constructor( ::AnimatingColorTransition ) - val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_secondary95) + val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_neutral20) val surfaceColor = animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor -> val colorList = ColorStateList.valueOf(surfaceColor) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index 9cdf85794088..992eeca77e54 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -40,6 +40,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -76,6 +77,7 @@ import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.util.Locale import java.util.TreeMap +import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope @@ -102,6 +104,7 @@ constructor( private val activityStarter: ActivityStarter, private val systemClock: SystemClock, @Main executor: DelayableExecutor, + @Background private val bgExecutor: Executor, private val mediaManager: MediaDataManager, configurationController: ConfigurationController, falsingManager: FalsingManager, @@ -1030,7 +1033,7 @@ constructor( desiredHostState?.let { if (this.desiredLocation != desiredLocation) { // Only log an event when location changes - logger.logCarouselPosition(desiredLocation) + bgExecutor.execute { logger.logCarouselPosition(desiredLocation) } } // This is a hosting view, let's remeasure our players diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 8d1ff98a5bba..62c7343d3fe5 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -31,7 +31,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; @@ -322,7 +321,6 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible()) .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, allowSystemGestureIgnoringBarVisibility()) - .setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode()) .commitUpdate(mDisplayId); } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 6d55d0549d2e..de490a58a498 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -27,7 +27,6 @@ import android.os.UserManager import android.util.Log import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -43,6 +42,7 @@ import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEnabledKey import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskInfoResolver +import com.android.systemui.res.R import com.android.systemui.stylus.StylusManager import dagger.Lazy import java.util.concurrent.Executor @@ -100,7 +100,8 @@ constructor( isEnabled && isUserUnlocked && isDefaultNotesAppSet && - (isConfigSelected || isStylusEverUsed) + isConfigSelected && + isStylusEverUsed ) { val contentDescription = ContentDescription.Resource(pickerNameResourceId) val icon = Icon.Resource(pickerIconResourceId, contentDescription) 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/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 38bbce45e143..17e6375967fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -19,6 +19,7 @@ import android.util.Log; import androidx.annotation.Nullable; +import com.android.systemui.accessibility.qs.QSAccessibilityModule; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; @@ -41,7 +42,7 @@ import javax.inject.Provider; * com.android.systemui.qs.tiles.DreamTile#TILE_SPEC}) * * After, create or find an existing Module class to house the tile's binding method (e.g. {@link - * com.android.systemui.accessibility.AccessibilityModule}). If creating a new module, add your + * QSAccessibilityModule}). If creating a new module, add your * module to the SystemUI dagger graph by including it in an appropriate module. */ @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt new file mode 100644 index 000000000000..0434b2d89e32 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -0,0 +1,154 @@ +/* + * 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.tiles + +import android.app.AlertDialog +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.service.quicksettings.Tile +import android.text.TextUtils +import android.view.View +import android.widget.Switch +import androidx.annotation.VisibleForTesting +import com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN +import com.android.internal.logging.MetricsLogger +import com.android.systemui.Flags.recordIssueQsTile +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.recordissue.RecordIssueDialogDelegate +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.KeyguardDismissUtil +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.policy.KeyguardStateController +import javax.inject.Inject + +class RecordIssueTile +@Inject +constructor( + host: QSHost, + uiEventLogger: QsEventLogger, + @Background backgroundLooper: Looper, + @Main mainHandler: Handler, + falsingManager: FalsingManager, + metricsLogger: MetricsLogger, + statusBarStateController: StatusBarStateController, + activityStarter: ActivityStarter, + qsLogger: QSLogger, + private val keyguardDismissUtil: KeyguardDismissUtil, + private val keyguardStateController: KeyguardStateController, + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val sysuiDialogFactory: SystemUIDialog.Factory, +) : + QSTileImpl<QSTile.BooleanState>( + host, + uiEventLogger, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger + ) { + + @VisibleForTesting var isRecording: Boolean = false + + override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label) + + override fun isAvailable(): Boolean = recordIssueQsTile() + + override fun newTileState(): QSTile.BooleanState = + QSTile.BooleanState().apply { + label = tileLabel + handlesLongClick = false + } + + @VisibleForTesting + public override fun handleClick(view: View?) { + if (isRecording) { + isRecording = false + } else { + mUiHandler.post { showPrompt(view) } + } + refreshState() + } + + private fun showPrompt(view: View?) { + val dialog: AlertDialog = + RecordIssueDialogDelegate(sysuiDialogFactory) { + isRecording = true + refreshState() + } + .createDialog() + val dismissAction = + ActivityStarter.OnDismissAction { + // We animate from the touched view only if we are not on the keyguard, given + // that if we are we will dismiss it which will also collapse the shade. + if (view != null && !keyguardStateController.isShowing) { + dialogLaunchAnimator.showFromView( + dialog, + view, + DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC) + ) + } else { + dialog.show() + } + false + } + keyguardDismissUtil.executeWhenUnlocked(dismissAction, false, true) + } + + override fun getLongClickIntent(): Intent? = null + + @VisibleForTesting + public override fun handleUpdateState(qsTileState: QSTile.BooleanState, arg: Any?) { + qsTileState.apply { + if (isRecording) { + value = true + state = Tile.STATE_ACTIVE + forceExpandIcon = false + secondaryLabel = mContext.getString(R.string.qs_record_issue_stop) + icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_on) + } else { + value = false + state = Tile.STATE_INACTIVE + forceExpandIcon = true + secondaryLabel = mContext.getString(R.string.qs_record_issue_start) + icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_off) + } + label = tileLabel + contentDescription = + if (TextUtils.isEmpty(secondaryLabel)) label else "$label, $secondaryLabel" + expandedAccessibilityClassName = Switch::class.java.name + } + } + + companion object { + const val TILE_SPEC = "record_issue" + } +} 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/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt new file mode 100644 index 000000000000..1efbfd70fa98 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.colorcorrection.domain + +import android.content.res.Resources +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [ColorCorrectionTileModel] to [QSTileState]. */ +class ColorCorrectionTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, +) : QSTileDataToStateMapper<ColorCorrectionTileModel> { + + override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction) + + if (data.isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = subtitleArray[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = subtitleArray[1] + } + contentDescription = label + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt new file mode 100644 index 000000000000..cd33d451ba81 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt @@ -0,0 +1,43 @@ +/* + * 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.tiles.impl.colorcorrection.domain.interactor + +import android.os.UserHandle +import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Observes color correction state changes providing the [ColorCorrectionTileModel]. */ +class ColorCorrectionTileDataInteractor +@Inject +constructor( + private val colorCorrectionRepository: ColorCorrectionRepository, +) : QSTileDataInteractor<ColorCorrectionTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<ColorCorrectionTileModel> { + return colorCorrectionRepository.isEnabled(user).map { ColorCorrectionTileModel(it) } + } + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt new file mode 100644 index 000000000000..d1838029db4e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.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.qs.tiles.impl.colorcorrection.domain.interactor + +import android.content.Intent +import android.provider.Settings +import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Handles color correction tile clicks. */ +class ColorCorrectionUserActionInteractor +@Inject +constructor( + private val colorCorrectionRepository: ColorCorrectionRepository, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<ColorCorrectionTileModel> { + + override suspend fun handleInput(input: QSTileInput<ColorCorrectionTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + colorCorrectionRepository.setIsEnabled( + !data.isEnabled, + user, + ) + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_COLOR_CORRECTION_SETTINGS) + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.kt new file mode 100644 index 000000000000..66487e1bba60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.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.tiles.impl.colorcorrection.domain.model + +/** + * Color correction tile model. + * + * @param isEnabled is true when the color correction is enabled; + */ +@JvmInline value class ColorCorrectionTileModel(val isEnabled: Boolean) 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/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt new file mode 100644 index 000000000000..fc42ba495a51 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.saver.domain + +import android.content.Context +import android.content.DialogInterface +import android.content.SharedPreferences +import android.os.Bundle +import com.android.internal.R +import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.policy.DataSaverController +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class DataSaverDialogDelegate( + private val sysuiDialogFactory: SystemUIDialog.Factory, + private val context: Context, + private val backgroundContext: CoroutineContext, + private val dataSaverController: DataSaverController, + private val sharedPreferences: SharedPreferences, +) : SystemUIDialog.Delegate { + override fun createDialog(): SystemUIDialog { + return sysuiDialogFactory.create(this, context) + } + + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + with(dialog) { + setTitle(R.string.data_saver_enable_title) + setMessage(R.string.data_saver_description) + setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ -> + CoroutineScope(backgroundContext).launch { + dataSaverController.setDataSaverEnabled(true) + } + + sharedPreferences + .edit() + .putBoolean(DataSaverTileUserActionInteractor.DIALOG_SHOWN, true) + .apply() + } + setNeutralButton(R.string.cancel, null) + setShowForAllUsers(true) + } + } +} 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 new file mode 100644 index 000000000000..df25600228a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt @@ -0,0 +1,57 @@ +/* + * 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.tiles.impl.saver.domain + +import android.content.res.Resources +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [DataSaverTileModel] to [QSTileState]. */ +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, theme, config.uiConfig) { + with(data) { + val iconRes: Int + if (isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + iconRes = R.drawable.qs_data_saver_icon_on + secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + 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/saver/domain/interactor/DataSaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt new file mode 100644 index 000000000000..91e049b68c06 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.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.tiles.impl.saver.domain.interactor + +import android.os.UserHandle +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.statusbar.policy.DataSaverController +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** Observes data saver state changes providing the [DataSaverTileModel]. */ +class DataSaverTileDataInteractor +@Inject +constructor( + private val dataSaverController: DataSaverController, +) : QSTileDataInteractor<DataSaverTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<DataSaverTileModel> = + ConflatedCallbackFlow.conflatedCallbackFlow { + val initialValue = dataSaverController.isDataSaverEnabled + trySend(DataSaverTileModel(initialValue)) + + val callback = DataSaverController.Listener { trySend(DataSaverTileModel(it)) } + + dataSaverController.addCallback(callback) + awaitClose { dataSaverController.removeCallback(callback) } + } + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt new file mode 100644 index 000000000000..af74409630ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.saver.domain.interactor + +import android.content.Context +import android.content.Intent +import android.provider.Settings +import com.android.internal.jank.InteractionJankMonitor +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverDialogDelegate +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.settings.UserFileManager +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.policy.DataSaverController +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext + +/** Handles data saver tile clicks. */ +class DataSaverTileUserActionInteractor +@Inject +constructor( + @Application private val context: Context, + @Main private val coroutineContext: CoroutineContext, + @Background private val backgroundContext: CoroutineContext, + private val dataSaverController: DataSaverController, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val systemUIDialogFactory: SystemUIDialog.Factory, + userFileManager: UserFileManager, +) : QSTileUserActionInteractor<DataSaverTileModel> { + companion object { + private const val INTERACTION_JANK_TAG = "start_data_saver" + const val PREFS = "data_saver" + const val DIALOG_SHOWN = "data_saver_dialog_shown" + } + + val sharedPreferences = + userFileManager.getSharedPreferences(PREFS, Context.MODE_PRIVATE, context.userId) + + override suspend fun handleInput(input: QSTileInput<DataSaverTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + val wasEnabled: Boolean = data.isEnabled + if (wasEnabled || sharedPreferences.getBoolean(DIALOG_SHOWN, false)) { + withContext(backgroundContext) { + dataSaverController.setDataSaverEnabled(!wasEnabled) + } + return@with + } + // Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator + // must be created and shown on the main thread, so we post it to the UI + // handler + withContext(coroutineContext) { + val dialogContext = action.view?.context ?: context + val dialogDelegate = + DataSaverDialogDelegate( + systemUIDialogFactory, + dialogContext, + backgroundContext, + dataSaverController, + sharedPreferences + ) + val dialog = systemUIDialogFactory.create(dialogDelegate, dialogContext) + + if (action.view != null) { + dialogLaunchAnimator.showFromView( + dialog, + action.view!!, + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG + ) + ) + } else { + dialog.show() + } + } + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_DATA_SAVER_SETTINGS) + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt new file mode 100644 index 000000000000..040c7bf55236 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.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.tiles.impl.saver.domain.model + +/** + * data saver tile model. + * + * @param isEnabled is true when the data saver is enabled; + */ +@JvmInline value class DataSaverTileModel(val isEnabled: Boolean) 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/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt new file mode 100644 index 000000000000..8221c635741b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recordissue + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.WindowManager +import android.widget.Button +import android.widget.PopupMenu +import android.widget.Switch +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIDialog + +class RecordIssueDialogDelegate( + private val factory: SystemUIDialog.Factory, + private val onStarted: Runnable +) : SystemUIDialog.Delegate { + + @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch + private lateinit var issueTypeButton: Button + + override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + dialog.apply { + setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null)) + setTitle(context.getString(R.string.qs_record_issue_label)) + setIcon(R.drawable.qs_record_issue_icon_off) + setNegativeButton(R.string.cancel) { _, _ -> dismiss() } + setPositiveButton(R.string.qs_record_issue_start) { _, _ -> + onStarted.run() + dismiss() + } + } + } + + override fun createDialog(): SystemUIDialog = factory.create(this) + + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + dialog.apply { + window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) + window?.setGravity(Gravity.CENTER) + + screenRecordSwitch = requireViewById(R.id.screenrecord_switch) + issueTypeButton = requireViewById(R.id.issue_type_button) + issueTypeButton.setOnClickListener { onIssueTypeClicked(context) } + } + } + + private fun onIssueTypeClicked(context: Context) { + val selectedCategory = issueTypeButton.text.toString() + val popupMenu = PopupMenu(context, issueTypeButton) + + context.resources.getStringArray(R.array.qs_record_issue_types).forEachIndexed { i, cat -> + popupMenu.menu.add(0, 0, i, cat).apply { + setIcon(R.drawable.arrow_pointing_down) + if (selectedCategory != cat) { + iconTintList = ColorStateList.valueOf(Color.TRANSPARENT) + } + } + } + popupMenu.apply { + setOnMenuItemClickListener { + issueTypeButton.text = it.title + true + } + setForceShowIcon(true) + show() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt new file mode 100644 index 000000000000..d67cf4d3d098 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt @@ -0,0 +1,33 @@ +/* + * 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.recordissue + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.RecordIssueTile +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface RecordIssueModule { + /** Inject RecordIssueTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(RecordIssueTile.TILE_SPEC) + fun bindRecordIssueTile(recordIssueTile: RecordIssueTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index e9779cd02760..5fbb60d76fbb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -59,6 +59,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.ui.view.WindowRootViewComponent; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.domain.interactor.ShadeInteractor; @@ -116,6 +117,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final AuthController mAuthController; private final Lazy<SelectedUserInteractor> mUserInteractor; private final Lazy<ShadeInteractor> mShadeInteractorLazy; + private final SceneContainerFlags mSceneContainerFlags; private ViewGroup mWindowRootView; private LayoutParams mLp; private boolean mHasTopUi; @@ -162,7 +164,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW Lazy<ShadeInteractor> shadeInteractorLazy, ShadeWindowLogger logger, Lazy<SelectedUserInteractor> userInteractor, - UserTracker userTracker) { + UserTracker userTracker, + SceneContainerFlags sceneContainerFlags) { mContext = context; mWindowRootViewComponentFactory = windowRootViewComponentFactory; mWindowManager = windowManager; @@ -180,6 +183,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW dumpManager.registerDumpable(this); mAuthController = authController; mUserInteractor = userInteractor; + mSceneContainerFlags = sceneContainerFlags; mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed(); mLockScreenDisplayTimeout = context.getResources() .getInteger(R.integer.config_lockScreenDisplayTimeout); @@ -287,6 +291,15 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED; mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + if (mSceneContainerFlags.isEnabled()) { + // This prevents the appearance and disappearance of the software keyboard (also known + // as the "IME") from scrolling/panning the window to make room for the keyboard. + // + // The scene container logic does its own adjustment and animation when the IME appears + // or disappears. + mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + } + mWindowManager.addView(mWindowRootView, mLp); mLpChanged.copyFrom(mLp); 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/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt index c59ef2632f15..d26fded19cc1 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt @@ -59,6 +59,11 @@ abstract class SmartspaceModule { * The BcSmartspaceDataPlugin for the standalone weather. */ const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin" + + /** + * The BcSmartspaceDataProvider for the glanceable hub. + */ + const val GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN = "glanceable_hub_smartspace_data_plugin" } @BindsOptionalOf @@ -78,4 +83,8 @@ abstract class SmartspaceModule { abstract fun bindSmartspacePrecondition( lockscreenPrecondition: LockscreenPrecondition? ): SmartspacePrecondition? + + @BindsOptionalOf + @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) + abstract fun optionalBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin? } diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt index 2fc0ec290a90..095d30ef55df 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt @@ -19,9 +19,9 @@ package com.android.systemui.smartspace.data.repository import android.app.smartspace.SmartspaceTarget import android.os.Parcelable import android.widget.RemoteViews +import com.android.systemui.communal.smartspace.CommunalSmartspaceController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.BcSmartspaceDataPlugin -import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -32,37 +32,37 @@ interface SmartspaceRepository { /** Whether [RemoteViews] are passed through smartspace targets. */ val isSmartspaceRemoteViewsEnabled: Boolean - /** Smartspace targets for the lockscreen surface. */ - val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> + /** Smartspace targets for the communal surface. */ + val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> } @SysUISingleton class SmartspaceRepositoryImpl @Inject constructor( - private val lockscreenSmartspaceController: LockscreenSmartspaceController, + private val communalSmartspaceController: CommunalSmartspaceController, ) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener { override val isSmartspaceRemoteViewsEnabled: Boolean get() = android.app.smartspace.flags.Flags.remoteViews() - private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = + private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = MutableStateFlow(emptyList()) - override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> = - _lockscreenSmartspaceTargets + override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> = + _communalSmartspaceTargets .onStart { - lockscreenSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl) + communalSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl) } .onCompletion { - lockscreenSmartspaceController.removeListener( + communalSmartspaceController.removeListener( listener = this@SmartspaceRepositoryImpl ) } override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) { targetsNullable?.let { targets -> - _lockscreenSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>() + _communalSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>() } - ?: run { _lockscreenSmartspaceTargets.value = emptyList() } + ?: run { _communalSmartspaceTargets.value = emptyList() } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java index d23c85a6d796..cfbd015f1cd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java @@ -18,16 +18,16 @@ package com.android.systemui.statusbar; import android.annotation.NonNull; import android.annotation.Nullable; -import android.os.Handler; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import android.view.accessibility.AccessibilityEvent; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; import java.util.stream.Stream; @@ -46,13 +46,12 @@ public abstract class AlertingNotificationManager { protected int mMinimumDisplayTime; protected int mStickyForSomeTimeAutoDismissTime; protected int mAutoDismissTime; - @VisibleForTesting - public Handler mHandler; + private DelayableExecutor mExecutor; - public AlertingNotificationManager(HeadsUpManagerLogger logger, @Main Handler handler, - SystemClock systemClock) { + public AlertingNotificationManager(HeadsUpManagerLogger logger, + SystemClock systemClock, @Main DelayableExecutor executor) { mLogger = logger; - mHandler = handler; + mExecutor = executor; mSystemClock = systemClock; } @@ -264,6 +263,7 @@ public abstract class AlertingNotificationManager { public long mEarliestRemovalTime; @Nullable protected Runnable mRemoveAlertRunnable; + @Nullable private Runnable mCancelRemoveAlertRunnable; public void setEntry(@NonNull final NotificationEntry entry) { setEntry(entry, () -> removeAlertEntry(entry.getKey())); @@ -291,13 +291,15 @@ public abstract class AlertingNotificationManager { if (updatePostTime) { mPostTime = Math.max(mPostTime, now); } - removeAutoRemovalCallbacks("updateEntry (will be rescheduled)"); - if (!isSticky()) { - final long finishTime = calculateFinishTime(); - final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime); - mHandler.postDelayed(mRemoveAlertRunnable, timeLeft); + if (isSticky()) { + removeAutoRemovalCallbacks("updateEntry (sticky)"); + return; } + + final long finishTime = calculateFinishTime(); + final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime); + scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)"); } /** @@ -340,21 +342,50 @@ public abstract class AlertingNotificationManager { * Clear any pending removal runnables. */ public void removeAutoRemovalCallbacks(@Nullable String reason) { - if (mRemoveAlertRunnable != null) { + final boolean removed = removeAutoRemovalCallbackInternal(); + + if (removed) { mLogger.logAutoRemoveCanceled(mEntry, reason); - mHandler.removeCallbacks(mRemoveAlertRunnable); } } + private void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) { + if (mRemoveAlertRunnable == null) { + Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set"); + return; + } + + final boolean removed = removeAutoRemovalCallbackInternal(); + + if (removed) { + mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason); + } else { + mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason); + } + + + mCancelRemoveAlertRunnable = mExecutor.executeDelayed(mRemoveAlertRunnable, + delayMillis); + } + + private boolean removeAutoRemovalCallbackInternal() { + final boolean scheduled = (mCancelRemoveAlertRunnable != null); + + if (scheduled) { + mCancelRemoveAlertRunnable.run(); + mCancelRemoveAlertRunnable = null; + } + + return scheduled; + } + /** * Remove the alert at the earliest allowed removal time. */ public void removeAsSoonAsPossible() { if (mRemoveAlertRunnable != null) { - removeAutoRemovalCallbacks("removeAsSoonAsPossible (will be rescheduled)"); - final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime(); - mHandler.postDelayed(mRemoveAlertRunnable, timeLeft); + scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index e486457b89bf..05c383910542 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_NULL; import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; @@ -46,6 +47,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -155,8 +157,22 @@ public class NotificationLockscreenUserManagerImpl implements if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { - boolean changed = updateDpcSettings(getSendingUserId()); - if (mCurrentUserId == getSendingUserId()) { + boolean changed = false; + int sendingUserId = getSendingUserId(); + if (sendingUserId == USER_ALL) { + // When a Device Owner triggers changes it's sent as USER_ALL. Normalize + // the user before calling into DPM + sendingUserId = mCurrentUserId; + @SuppressLint("MissingPermission") + List<UserInfo> users = mUserManager.getUsers(); + for (int i = users.size() - 1; i >= 0; i--) { + changed |= updateDpcSettings(users.get(i).id); + } + } else { + changed |= updateDpcSettings(sendingUserId); + } + + if (mCurrentUserId == sendingUserId) { changed |= updateLockscreenNotificationSetting(); } if (changed) { @@ -374,13 +390,13 @@ public class NotificationLockscreenUserManagerImpl implements mContext.getContentResolver().registerContentObserver( SHOW_LOCKSCREEN, false, mLockscreenSettingsObserver, - UserHandle.USER_ALL); + USER_ALL); mContext.getContentResolver().registerContentObserver( SHOW_PRIVATE_LOCKSCREEN, true, mLockscreenSettingsObserver, - UserHandle.USER_ALL); + USER_ALL); if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { mContext.getContentResolver().registerContentObserver( @@ -441,7 +457,7 @@ public class NotificationLockscreenUserManagerImpl implements public boolean isCurrentProfile(int userId) { synchronized (mLock) { - return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; + return userId == USER_ALL || mCurrentProfiles.get(userId) != null; } } @@ -526,7 +542,7 @@ public class NotificationLockscreenUserManagerImpl implements */ public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { - if (userHandle == UserHandle.USER_ALL) { + if (userHandle == USER_ALL) { userHandle = mCurrentUserId; } if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { @@ -540,7 +556,7 @@ public class NotificationLockscreenUserManagerImpl implements return mUsersUsersAllowingPrivateNotifications.get(userHandle) && mUsersDpcAllowingPrivateNotifications.get(userHandle); } else { - if (userHandle == UserHandle.USER_ALL) { + if (userHandle == USER_ALL) { return true; } @@ -574,7 +590,7 @@ public class NotificationLockscreenUserManagerImpl implements } private boolean adminAllowsKeyguardFeature(int userHandle, int feature) { - if (userHandle == UserHandle.USER_ALL) { + if (userHandle == USER_ALL) { return true; } final int dpmFlags = @@ -591,7 +607,7 @@ public class NotificationLockscreenUserManagerImpl implements } public boolean isLockscreenPublicMode(int userId) { - if (userId == UserHandle.USER_ALL) { + if (userId == USER_ALL) { return mLockscreenPublicMode.get(mCurrentUserId, false); } return mLockscreenPublicMode.get(userId, false); @@ -610,7 +626,7 @@ public class NotificationLockscreenUserManagerImpl implements if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { // Unlike 'show private', settings does not show a copy of this setting for each // profile, so it inherits from the parent user. - if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) { + if (userHandle == USER_ALL || mCurrentManagedProfiles.contains(userHandle)) { userHandle = mCurrentUserId; } if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) { 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/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt index a3adea0b86d9..642eaccc3c99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt @@ -35,6 +35,10 @@ import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper +import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor +import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig @@ -85,6 +89,7 @@ interface ConnectivityModule { companion object { const val AIRPLANE_MODE_TILE_SPEC = "airplane" + const val DATA_SAVER_TILE_SPEC = "saver" /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */ @Provides @@ -132,5 +137,36 @@ interface ConnectivityModule { stateInteractor, mapper, ) + + @Provides + @IntoMap + @StringKey(DATA_SAVER_TILE_SPEC) + fun provideDataSaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(DATA_SAVER_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_data_saver_icon_off, + labelRes = R.string.data_saver, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject DataSaverTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(DATA_SAVER_TILE_SPEC) + fun provideDataSaverTileViewModel( + factory: QSTileViewModelFactory.Static<DataSaverTileModel>, + mapper: DataSaverTileMapper, + stateInteractor: DataSaverTileDataInteractor, + userActionInteractor: DataSaverTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(DATA_SAVER_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 92391e7c76f0..e1e30e1d74f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.icon.ui.viewbinder import android.graphics.Color import android.graphics.Rect +import android.util.Log import android.view.View +import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.ColorInt import androidx.collection.ArrayMap @@ -220,7 +222,7 @@ object NotificationIconContainerViewBinder { notifyBindingFailures: (Collection<String>) -> Unit, viewStore: IconViewStore, bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> }, - ): Unit = coroutineScope { + ) { val iconSizeFlow: Flow<Int> = configuration.getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size_sp, @@ -235,6 +237,21 @@ object NotificationIconContainerViewBinder { -> FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight) } + try { + bindIcons(view, layoutParams, notifyBindingFailures, viewStore, bindIcon) + } finally { + // Detach everything so that child SBIVs don't hold onto a reference to the container. + view.detachAllIcons() + } + } + + private suspend fun Flow<NotificationIconsViewData>.bindIcons( + view: NotificationIconContainer, + layoutParams: Flow<FrameLayout.LayoutParams>, + notifyBindingFailures: (Collection<String>) -> Unit, + viewStore: IconViewStore, + bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit, + ): Unit = coroutineScope { val failedBindings = mutableSetOf<String>() val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>() var prevIcons = NotificationIconsViewData() @@ -266,9 +283,17 @@ object NotificationIconContainerViewBinder { continue } failedBindings.remove(notifKey) - // The view might still be transiently added if it was just removed and added - // again - view.removeTransientView(sbiv) + (sbiv.parent as? ViewGroup)?.run { + if (this !== view) { + Log.wtf(TAG, "StatusBarIconView($notifKey) has an unexpected parent") + } + // If the container was re-inflated and re-bound, then SBIVs might still be + // attached to the prior view. + removeView(sbiv) + // The view might still be transiently added if it was just removed and + // added again. + removeTransientView(sbiv) + } view.addView(sbiv, idx) boundViewsByNotifKey.remove(notifKey)?.second?.cancel() boundViewsByNotifKey[notifKey] = @@ -351,7 +376,8 @@ object NotificationIconContainerViewBinder { fun iconView(key: String): StatusBarIconView? } - @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE + @ColorInt private const val DEFAULT_AOD_ICON_COLOR = Color.WHITE + private const val TAG = "NotifIconContainerViewBinder" } /** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt index abf09ae9844c..e78a694735e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -26,5 +26,8 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class NotificationStackAppearanceRepository @Inject constructor() { /** The bounds of the notification stack in the current scene. */ - val stackBounds = MutableStateFlow(NotificationContainerBounds(0f, 0f)) + val stackBounds = MutableStateFlow(NotificationContainerBounds()) + + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp = MutableStateFlow(32f) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 32e4e89c42c5..61a4dfcbd201 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -39,4 +39,7 @@ constructor( check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } repository.stackBounds.value = bounds } + + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow() } 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/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt index fa7a8fdb7495..a9b542dcce2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -16,14 +16,16 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder +import android.content.Context +import android.util.TypedValue import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel +import kotlin.math.roundToInt import kotlinx.coroutines.launch /** Binds the shared notification container to its view-model. */ @@ -31,9 +33,9 @@ object NotificationStackAppearanceViewBinder { @JvmStatic fun bind( + context: Context, view: SharedNotificationContainer, viewModel: NotificationStackAppearanceViewModel, - sceneContainerFlags: SceneContainerFlags, ambientState: AmbientState, controller: NotificationStackScrollLayoutController, ) { @@ -45,6 +47,14 @@ object NotificationStackAppearanceViewBinder { bounds.top, controller.isAddOrRemoveAnimationPending ) + controller.setRoundedClippingBounds( + it.left, + it.top, + it.right, + it.bottom, + viewModel.cornerRadiusDp.value.dpToPx(context), + viewModel.cornerRadiusDp.value.dpToPx(context), + ) } } launch { @@ -56,4 +66,13 @@ object NotificationStackAppearanceViewBinder { } } } + + private fun Float.dpToPx(context: Context): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this, + context.resources.displayMetrics + ) + .roundToInt() + } } 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/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index f4c0e92b0e87..834d3ffe63c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */ @SysUISingleton @@ -37,4 +38,7 @@ constructor( /** The bounds of the notification stack in the current scene. */ val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds + + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index c6fd98ea2223..9f22118e3332 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow /** * ViewModel used by the Notification placeholders inside the scene container to update the @@ -55,9 +56,14 @@ constructor( * pixels. */ fun onBoundsChanged( + left: Float, top: Float, + right: Float, bottom: Float, ) { - interactor.setStackBounds(NotificationContainerBounds(top, bottom)) + interactor.setStackBounds(NotificationContainerBounds(left, top, right, bottom)) } + + /** The corner radius of the placeholder, in dp. */ + val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp } 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 5b854e6c8ee7..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 @@ -177,8 +177,8 @@ constructor( } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = NotificationContainerBounds(0f, 0f), + started = SharingStarted.Lazily, + initialValue = NotificationContainerBounds(), ) val alpha: Flow<Float> = @@ -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/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 644c8962b93d..e66d9e8ca531 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; @@ -119,12 +120,13 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp @Main Handler handler, GlobalSettings globalSettings, SystemClock systemClock, + @Main DelayableExecutor executor, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, JavaAdapter javaAdapter, ShadeInteractor shadeInteractor) { - super(context, logger, handler, globalSettings, systemClock, accessibilityManagerWrapper, - uiEventLogger); + super(context, logger, handler, globalSettings, systemClock, executor, + accessibilityManagerWrapper, uiEventLogger); Resources resources = mContext.getResources(); mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time); statusBarStateController.addCallback(mStatusBarStateListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 00e78a49ba19..0dabafbdecb0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -400,6 +400,21 @@ public class NotificationIconContainer extends ViewGroup { } } + /** + * Removes all child {@link StatusBarIconView} instances from this container, immediately and + * without animation. This should be called when tearing down this container so that external + * icon views are not holding onto a reference thru {@link View#getParent()}. + */ + public void detachAllIcons() { + boolean animsWereEnabled = mAnimationsEnabled; + boolean wasChangingPositions = mChangingViewPositions; + mAnimationsEnabled = false; + mChangingViewPositions = true; + removeAllViews(); + mChangingViewPositions = wasChangingPositions; + mAnimationsEnabled = animsWereEnabled; + } + public boolean areIconsOverflowing() { return mIsShowingOverflowDot; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt index 3b96f5793fe8..877bd7c11e95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -42,6 +42,9 @@ import com.android.systemui.util.leak.RotationUtils.Rotation import com.android.systemui.util.leak.RotationUtils.getExactRotation import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation import com.android.app.tracing.traceSection +import com.android.systemui.BottomMarginCommand +import com.android.systemui.StatusBarInsetsCommand +import com.android.systemui.statusbar.commandline.CommandRegistry import java.io.PrintWriter import java.lang.Math.max import javax.inject.Inject @@ -64,7 +67,8 @@ import javax.inject.Inject class StatusBarContentInsetsProvider @Inject constructor( val context: Context, val configurationController: ConfigurationController, - val dumpManager: DumpManager + val dumpManager: DumpManager, + val commandRegistry: CommandRegistry, ) : CallbackController<StatusBarContentInsetsChangedListener>, ConfigurationController.ConfigurationListener, Dumpable { @@ -80,6 +84,13 @@ class StatusBarContentInsetsProvider @Inject constructor( init { configurationController.addCallback(this) dumpManager.registerDumpable(TAG, this) + commandRegistry.registerCommand(StatusBarInsetsCommand.NAME) { + StatusBarInsetsCommand(object : StatusBarInsetsCommand.Callback { + override fun onExecute(command: StatusBarInsetsCommand, printWriter: PrintWriter) { + executeCommand(command, printWriter) + } + }) + } } override fun addCallback(listener: StatusBarContentInsetsChangedListener) { @@ -271,8 +282,41 @@ class StatusBarContentInsetsProvider @Inject constructor( statusBarContentHeight) } + private fun executeCommand(command: StatusBarInsetsCommand, printWriter: PrintWriter) { + command.bottomMargin?.let { executeBottomMarginCommand(it, printWriter) } + } + + private fun executeBottomMarginCommand(command: BottomMarginCommand, printWriter: PrintWriter) { + val rotation = command.rotationValue + if (rotation == null) { + printWriter.println( + "Rotation should be one of ${BottomMarginCommand.ROTATION_DEGREES_OPTIONS}" + ) + return + } + val marginBottomDp = command.marginBottomDp + if (marginBottomDp == null) { + printWriter.println("Margin bottom not set.") + return + } + setBottomMarginOverride(rotation, marginBottomDp) + } + + private val marginBottomOverrides = mutableMapOf<Int, Int>() + + private fun setBottomMarginOverride(rotation: Int, marginBottomDp: Float) { + insetsCache.evictAll() + val marginBottomPx = (marginBottomDp * context.resources.displayMetrics.density).toInt() + marginBottomOverrides[rotation] = marginBottomPx + notifyInsetsChanged() + } + @Px private fun getBottomAlignedMargin(targetRotation: Int, resources: Resources): Int { + val override = marginBottomOverrides[targetRotation] + if (override != null) { + return override + } val dimenRes = when (targetRotation) { Surface.ROTATION_0 -> R.dimen.status_bar_bottom_aligned_margin_rotation_0 @@ -294,6 +338,7 @@ class StatusBarContentInsetsProvider @Inject constructor( pw.println("$key -> $rect") } pw.println(insetsCache) + pw.println("Bottom margin overrides: $marginBottomOverrides") } private fun getCacheKey( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index daadedb06187..4999123247a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -36,6 +36,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; +import android.view.WindowInsetsController; import android.window.BackEvent; import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedDispatcher; @@ -859,10 +860,19 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } + private void setRootViewAnimationDisabled(boolean disabled) { + ViewGroup windowRootView = mNotificationShadeWindowController.getWindowRootView(); + if (windowRootView != null) { + WindowInsetsController insetsController = windowRootView.getWindowInsetsController(); + if (insetsController != null) { + insetsController.setAnimationsDisabled(disabled); + } + } + } + @Override public void onStartedWakingUp() { - mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController() - .setAnimationsDisabled(false); + setRootViewAnimationDisabled(false); NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView(); if (navBarView != null) { navBarView.forEachView(view -> @@ -875,8 +885,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onStartedGoingToSleep() { - mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController() - .setAnimationsDisabled(true); + setRootViewAnimationDisabled(true); NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView(); if (navBarView != null) { navBarView.forEachView(view -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt index 99b123fbd702..ae58398753e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt @@ -161,13 +161,13 @@ constructor( if (it == null) { notConnectedFlow } else { - val secondary = it.contentDescription.toString() + val secondary = it.contentDescription flowOf( InternetTileModel.Active( - secondaryTitle = secondary, + secondaryLabel = secondary?.toText(), iconId = it.res, stateDescription = null, - contentDescription = ContentDescription.Loaded(secondary), + contentDescription = secondary, ) ) } @@ -241,5 +241,11 @@ constructor( string.substring(1, length - 1) } else string } + + private fun ContentDescription.toText(): Text = + when (this) { + is ContentDescription.Loaded -> Text.Loaded(this.description) + is ContentDescription.Resource -> Text.Resource(this.res) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 8054b04529c8..e9711284cfca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.util.ListenerSet; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; @@ -87,9 +88,10 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp @Main Handler handler, GlobalSettings globalSettings, SystemClock systemClock, + @Main DelayableExecutor executor, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger) { - super(logger, handler, systemClock); + super(logger, systemClock, executor); mContext = context; mAccessibilityMgr = accessibilityManagerWrapper; mUiEventLogger = uiEventLogger; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index ef07eed467dc..f6154afec273 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -66,6 +66,26 @@ class HeadsUpManagerLogger @Inject constructor( }) } + fun logAutoRemoveScheduled(entry: NotificationEntry, delayMillis: Long, reason: String) { + buffer.log(TAG, INFO, { + str1 = entry.logKey + long1 = delayMillis + str2 = reason + }, { + "schedule auto remove of $str1 in $long1 ms reason: $str2" + }) + } + + fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) { + buffer.log(TAG, INFO, { + str1 = entry.logKey + long1 = delayMillis + str2 = reason + }, { + "reschedule auto remove of $str1 in $long1 ms reason: $str2" + }) + } + fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) { buffer.log(TAG, INFO, { str1 = entry.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 80c68023b4c8..0c5472f0ecfb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -41,6 +41,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -49,6 +50,7 @@ import dagger.Lazy; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Objects; +import java.util.function.Consumer; import javax.inject.Inject; @@ -210,23 +212,33 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private void notifyKeyguardChanged() { Trace.beginSection("KeyguardStateController#notifyKeyguardChanged"); // Copy the list to allow removal during callback. - new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged); + invokeForEachCallback(Callback::onKeyguardShowingChanged); Trace.endSection(); } private void notifyKeyguardFaceAuthEnabledChanged() { + invokeForEachCallback(Callback::onFaceEnrolledChanged); + } + + private void invokeForEachCallback(Consumer<Callback> consumer) { // Copy the list to allow removal during callback. - new ArrayList<>(mCallbacks).forEach(callback -> { + ArrayList<Callback> copyOfCallbacks = new ArrayList<>(mCallbacks); + for (int i = 0; i < copyOfCallbacks.size(); i++) { + Callback callback = copyOfCallbacks.get(i); + // Temporary fix for b/315731775, callback is null even though only non-null callbacks + // are added to the list by addCallback if (callback != null) { - callback.onFaceEnrolledChanged(); + consumer.accept(callback); + } else { + mLogger.log("KeyguardStateController callback is null", LogLevel.DEBUG); } - }); + } } private void notifyUnlockedChanged() { Trace.beginSection("KeyguardStateController#notifyUnlockedChanged"); // Copy the list to allow removal during callback. - new ArrayList<>(mCallbacks).forEach(Callback::onUnlockedChanged); + invokeForEachCallback(Callback::onUnlockedChanged); Trace.endSection(); } @@ -242,10 +254,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardFadingAway", keyguardFadingAway ? 1 : 0); mKeyguardFadingAway = keyguardFadingAway; - ArrayList<Callback> callbacks = new ArrayList<>(mCallbacks); - for (int i = 0; i < callbacks.size(); i++) { - callbacks.get(i).onKeyguardFadingAwayChanged(); - } + invokeForEachCallback(Callback::onKeyguardFadingAwayChanged); } } @@ -359,7 +368,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway", keyguardGoingAway ? 1 : 0); mKeyguardGoingAway = keyguardGoingAway; - new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardGoingAwayChanged); + invokeForEachCallback(Callback::onKeyguardGoingAwayChanged); } } @@ -368,7 +377,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum if (mPrimaryBouncerShowing != showing) { mPrimaryBouncerShowing = showing; - new ArrayList<>(mCallbacks).forEach(Callback::onPrimaryBouncerShowingChanged); + invokeForEachCallback(Callback::onPrimaryBouncerShowingChanged); } } @@ -392,13 +401,13 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum boolean dismissingFromTouch) { mDismissAmount = dismissAmount; mDismissingFromTouch = dismissingFromTouch; - new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardDismissAmountChanged); + invokeForEachCallback(Callback::onKeyguardDismissAmountChanged); } @Override public void setLaunchTransitionFadingAway(boolean fadingAway) { mLaunchTransitionFadingAway = fadingAway; - new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged); + invokeForEachCallback(Callback::onLaunchTransitionFadingAwayChanged); } @Override @@ -484,7 +493,12 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override - public void onBiometricsCleared() { + public void onFingerprintsCleared() { + update(false /* alwaysUpdate */); + } + + @Override + public void onFacesCleared() { update(false /* alwaysUpdate */); } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 886fa70d715d..2b9ad50c1257 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -18,8 +18,8 @@ package com.android.systemui.theme; import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; -import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation; +import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK; import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET; @@ -364,15 +364,23 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - boolean newWorkProfile = Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction()); - boolean isManagedProfile = mUserManager.isManagedProfile( - intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - if (newWorkProfile) { - if (!mDeviceProvisionedController.isCurrentUserSetup() && isManagedProfile) { + boolean newProfile = Intent.ACTION_PROFILE_ADDED.equals(intent.getAction()); + if (newProfile) { + UserHandle newUserHandle = intent.getParcelableExtra(Intent.EXTRA_USER, + android.os.UserHandle.class); + boolean isManagedProfile = + mUserManager.isManagedProfile(newUserHandle.getIdentifier()); + if (!mDeviceProvisionedController.isUserSetup(newUserHandle.getIdentifier()) + && isManagedProfile) { Log.i(TAG, "User setup not finished when " + intent.getAction() + " was received. Deferring... Managed profile? " + isManagedProfile); return; } + if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) { + mDeferredThemeEvaluation = true; + Log.i(TAG, "Deferring theme for private profile till user setup is complete"); + return; + } if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); reevaluateSystemTheme(true /* forceReload */); } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) { @@ -432,7 +440,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { public void start() { if (DEBUG) Log.d(TAG, "Start"); final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); + filter.addAction(Intent.ACTION_PROFILE_ADDED); filter.addAction(Intent.ACTION_WALLPAPER_CHANGED); mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor, UserHandle.ALL); @@ -608,6 +616,15 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { return new FabricatedOverlay.Builder("com.android.systemui", name, "android").build(); } + @VisibleForTesting + protected boolean isPrivateProfile(UserHandle userHandle) { + Context usercontext = mContext.createContextAsUser(userHandle,0); + if (usercontext.getSystemService(UserManager.class).isPrivateProfile()) { + return true; + } + return false; + } + private void createOverlays(int color) { boolean nightMode = isNightMode(); mColorScheme = new ColorScheme(color, nightMode, mThemeStyle); @@ -784,7 +801,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { Set<UserHandle> managedProfiles = new HashSet<>(); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { - if (userInfo.isManagedProfile()) { + if (userInfo.isProfile()) { managedProfiles.add(userInfo.getUserHandle()); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index f5edb7bb5b73..fa6d0552c9e9 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -83,17 +83,19 @@ public class Utils { /** * Allow the media player to be shown in the QS area, controlled by 2 flags. - * Off by default, but can be disabled by setting to 0 + * On by default, but can be disabled by setting either flag to 0/false. */ public static boolean useQsMediaPlayer(Context context) { - // TODO(b/192412820): Replace SHOW_MEDIA_ON_QUICK_SETTINGS with compile-time value // Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS can't be toggled at runtime, so simply // cache the first result we fetch and use that going forward. Do this to avoid unnecessary // binder calls which may happen on the critical path. if (sUseQsMediaPlayer == null) { - int flag = Settings.Global.getInt(context.getContentResolver(), + // TODO(b/192412820): Consolidate SHOW_MEDIA_ON_QUICK_SETTINGS into compile-time value. + final int settingsFlag = Settings.Global.getInt(context.getContentResolver(), Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); - sUseQsMediaPlayer = flag > 0; + final boolean configFlag = context.getResources() + .getBoolean(com.android.internal.R.bool.config_quickSettingsShowMediaPlayer); + sUseQsMediaPlayer = settingsFlag > 0 && configFlag; } return sUseQsMediaPlayer; } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt new file mode 100644 index 000000000000..2f6c450e924c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt @@ -0,0 +1,40 @@ +/* + * 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.util.kotlin + +import android.util.Log + +/** Logs message at [Log.DEBUG] level. Won't call the lambda if [DEBUG] is not loggable. */ +inline fun logD(tag: String, messageLambda: () -> String) { + if (Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, messageLambda.invoke()) + } +} + +/** Logs message at [Log.VERBOSE] level. Won't call the lambda if [VERBOSE] is not loggable. */ +inline fun logV(tag: String, messageLambda: () -> String) { + if (Log.isLoggable(tag, Log.VERBOSE)) { + Log.v(tag, messageLambda.invoke()) + } +} + +/** Logs message at [Log.INFO] level. Won't call the lambda if [INFO] is not loggable. */ +inline fun logI(tag: String, messageLambda: () -> String) { + if (Log.isLoggable(tag, Log.INFO)) { + Log.i(tag, messageLambda.invoke()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index 235aa218715d..f8856c968a23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -34,8 +34,8 @@ import android.testing.TestableLooper; import android.view.Display; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IMagnificationConnection; +import android.view.accessibility.IMagnificationConnectionCallback; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnectionCallback; import androidx.test.filters.SmallTest; @@ -67,7 +67,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Mock private CommandQueue mCommandQueue; @Mock - private IWindowMagnificationConnectionCallback mConnectionCallback; + private IMagnificationConnectionCallback mConnectionCallback; @Mock private WindowMagnificationController mWindowMagnificationController; @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java index 39c8f5d724b0..d0e1678739c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java @@ -44,7 +44,7 @@ import android.testing.TestableLooper; import android.view.Display; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IMagnificationConnection; -import android.view.accessibility.IWindowMagnificationConnectionCallback; +import android.view.accessibility.IMagnificationConnectionCallback; import androidx.test.filters.SmallTest; @@ -75,7 +75,7 @@ public class MagnificationTest extends SysuiTestCase { @Mock private SysUiState mSysUiState; @Mock - private IWindowMagnificationConnectionCallback mConnectionCallback; + private IMagnificationConnectionCallback mConnectionCallback; @Mock private OverviewProxyService mOverviewProxyService; @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index ea20d29556dc..91219f02818c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -47,12 +47,14 @@ import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorI import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.events.ANIMATING_OUT +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -101,6 +103,12 @@ open class AuthContainerViewTest : SysuiTestCase() { lateinit var interactionJankMonitor: InteractionJankMonitor @Mock lateinit var vibrator: VibratorHelper + @Mock + lateinit var udfpsUtils: UdfpsUtils + @Mock + lateinit var authController: AuthController + @Mock + lateinit var selectedUserInteractor: SelectedUserInteractor private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) @@ -123,6 +131,7 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var displayRepository: FakeDisplayRepository private lateinit var displayStateInteractor: DisplayStateInteractor + private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) @@ -140,6 +149,12 @@ open class AuthContainerViewTest : SysuiTestCase() { displayStateRepository, displayRepository, ) + udfpsOverlayInteractor = + UdfpsOverlayInteractor( + authController, + selectedUserInteractor, + testScope.backgroundScope, + ) } @After @@ -532,6 +547,8 @@ open class AuthContainerViewTest : SysuiTestCase() { displayStateInteractor, promptSelectorInteractor, context, + udfpsOverlayInteractor, + udfpsUtils ), { credentialViewModel }, Handler(TestableLooper.get(this).looper), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt index 22e3e7fedda0..74c43131b955 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics.shared.model +import android.hardware.fingerprint.FingerprintSensorProperties import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.faceSensorPropertiesInternal @@ -35,6 +36,46 @@ class BiometricModalitiesTest : SysuiTestCase() { } @Test + fun hasUdfps() { + with( + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal( + sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL + ).first(), + ) + ) { + assertThat(isEmpty).isFalse() + assertThat(hasUdfps).isTrue() + assertThat(hasSfps).isFalse() + assertThat(hasFace).isFalse() + assertThat(hasFaceOnly).isFalse() + assertThat(hasFingerprint).isTrue() + assertThat(hasFingerprintOnly).isTrue() + assertThat(hasFaceAndFingerprint).isFalse() + } + } + + @Test + fun hasSfps() { + with( + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal( + sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + ).first(), + ) + ) { + assertThat(isEmpty).isFalse() + assertThat(hasUdfps).isFalse() + assertThat(hasSfps).isTrue() + assertThat(hasFace).isFalse() + assertThat(hasFaceOnly).isFalse() + assertThat(hasFingerprint).isTrue() + assertThat(hasFingerprintOnly).isTrue() + assertThat(hasFaceAndFingerprint).isFalse() + } + } + + @Test fun fingerprintOnly() { with( BiometricModalities( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index d06cbbb5e433..7475235cfeae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.ui.viewmodel import android.content.res.Configuration +import android.graphics.Point import android.hardware.biometrics.PromptInfo import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorProperties @@ -25,7 +26,10 @@ import android.view.HapticFeedbackConstants import android.view.MotionEvent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags.FLAG_BP_TALKBACK import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController +import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository @@ -33,6 +37,7 @@ import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.extractAuthenticatorTypes import com.android.systemui.biometrics.faceSensorPropertiesInternal import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal @@ -45,8 +50,10 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.res.R -import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -77,7 +84,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var lockPatternUtils: LockPatternUtils - @Mock private lateinit var vibrator: VibratorHelper + @Mock private lateinit var authController: AuthController + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var udfpsUtils: UdfpsUtils private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val testScope = TestScope() @@ -87,6 +96,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private lateinit var displayStateRepository: FakeDisplayStateRepository private lateinit var displayRepository: FakeDisplayRepository private lateinit var displayStateInteractor: DisplayStateInteractor + private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private lateinit var selector: PromptSelectorInteractor private lateinit var viewModel: PromptViewModel @@ -116,11 +126,24 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa displayStateRepository, displayRepository, ) + udfpsOverlayInteractor = + UdfpsOverlayInteractor( + authController, + selectedUserInteractor, + testScope.backgroundScope + ) selector = PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) selector.resetPrompt() - viewModel = PromptViewModel(displayStateInteractor, selector, mContext) + viewModel = + PromptViewModel( + displayStateInteractor, + selector, + mContext, + udfpsOverlayInteractor, + udfpsUtils + ) iconViewModel = viewModel.iconViewModel } @@ -1153,6 +1176,29 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(size).isEqualTo(PromptSize.LARGE) } + @Test + fun hint_for_talkback_guidance() = runGenericTest { + mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK) + val hint by collectLastValue(viewModel.accessibilityHint) + + // Touches should fall outside of sensor area + whenever(udfpsUtils.getTouchInNativeCoordinates(any(), any(), any())) + .thenReturn(Point(0, 0)) + whenever(udfpsUtils.onTouchOutsideOfSensorArea(any(), any(), any(), any(), any())) + .thenReturn("Direction") + + viewModel.onAnnounceAccessibilityHint( + obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER), + true + ) + + if (testCase.modalities.hasUdfps) { + assertThat(hint?.isNotBlank()).isTrue() + } else { + assertThat(hint.isNullOrBlank()).isTrue() + } + } + /** Asserts that the selected buttons are visible now. */ private suspend fun TestScope.assertButtonsVisible( tryAgain: Boolean = false, @@ -1220,14 +1266,19 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa authenticatedModality = BiometricModality.Face, ), TestCase( - fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + fingerprint = + fingerprintSensorPropertiesInternal( + strong = true, + sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + ) + .first(), authenticatedModality = BiometricModality.Fingerprint, ), TestCase( fingerprint = fingerprintSensorPropertiesInternal( strong = true, - sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL ) .first(), authenticatedModality = BiometricModality.Fingerprint, @@ -1264,19 +1315,29 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa TestCase( face = faceSensorPropertiesInternal(strong = true).first(), fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), - authenticatedModality = BiometricModality.Fingerprint, + authenticatedModality = BiometricModality.Face, + confirmationRequested = true, ), TestCase( face = faceSensorPropertiesInternal(strong = true).first(), - fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), - authenticatedModality = BiometricModality.Face, + fingerprint = + fingerprintSensorPropertiesInternal( + strong = true, + sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON + ) + .first(), + authenticatedModality = BiometricModality.Fingerprint, confirmationRequested = true, ), TestCase( face = faceSensorPropertiesInternal(strong = true).first(), - fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(), + fingerprint = + fingerprintSensorPropertiesInternal( + strong = true, + sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL + ) + .first(), authenticatedModality = BiometricModality.Fingerprint, - confirmationRequested = true, ), ) } @@ -1309,6 +1370,9 @@ internal data class TestCase( else -> false } + val modalities: BiometricModalities + get() = BiometricModalities(fingerprint, face) + val authenticatedByFingerprint: Boolean get() = authenticatedModality == BiometricModality.Fingerprint diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt index 395d7129bee3..ca9582240b93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt @@ -18,10 +18,10 @@ package com.android.systemui.bouncer.ui.helper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER import com.google.common.truth.Truth.assertThat import java.util.Locale import org.junit.Test @@ -84,33 +84,33 @@ class BouncerSceneLayoutTest : SysuiTestCase() { listOf( Phone to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), Tablet to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = STACKED, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = BELOW_USER_SWITCHER, ), Folded to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), Unfolded to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = STANDARD, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = STANDARD_BOUNCER, ), TallerFolded to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), TallerUnfolded to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = SIDE_BY_SIDE, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = BESIDE_USER_SWITCHER, ), ) .flatMap { (device, expected) -> @@ -124,13 +124,13 @@ class BouncerSceneLayoutTest : SysuiTestCase() { ) ) - if (expected.whenNaturallyHeld == SIDE_BY_SIDE) { + if (expected.whenNaturallyHeld == BESIDE_USER_SWITCHER) { add( TestCase( device = device, held = device.naturallyHeld, isSideBySideSupported = false, - expected = STANDARD, + expected = STANDARD_BOUNCER, ) ) } @@ -144,13 +144,13 @@ class BouncerSceneLayoutTest : SysuiTestCase() { ) ) - if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) { + if (expected.whenUnnaturallyHeld == BESIDE_USER_SWITCHER) { add( TestCase( device = device, held = device.naturallyHeld.flip(), isSideBySideSupported = false, - expected = STANDARD, + expected = STANDARD_BOUNCER, ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java index 220718027eee..07c980bb6656 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java @@ -21,14 +21,13 @@ import static com.android.systemui.controls.dagger.ControlsComponent.Visibility. import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; -import android.content.Context; import android.content.res.Resources; import android.testing.AndroidTestingRunner; import android.view.View; @@ -48,6 +47,7 @@ import com.android.systemui.controls.management.ControlsListingController; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.Before; import org.junit.Test; @@ -71,9 +71,6 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { private DreamOverlayStateController mDreamOverlayStateController; @Mock - private Context mContext; - - @Mock private Resources mResources; @Mock @@ -100,6 +97,9 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; + @Mock + private ConfigurationController mConfigurationController; + @Captor private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor; @@ -109,7 +109,8 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - when(mContext.getString(anyInt())).thenReturn(""); + mContext.ensureTestableResources(); + when(mControlsComponent.getControlsController()).thenReturn( Optional.of(mControlsController)); when(mControlsComponent.getControlsListingController()).thenReturn( @@ -225,6 +226,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { mHomeControlsView, mActivityStarter, mContext, + mConfigurationController, mControlsComponent, mUiEventLogger); viewController.onViewAttached(); @@ -237,6 +239,24 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED); } + @Test + public void testUnregistersConfigurationCallback() { + final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController = + new DreamHomeControlsComplication.DreamHomeControlsChipViewController( + mHomeControlsView, + mActivityStarter, + mContext, + mConfigurationController, + mControlsComponent, + mUiEventLogger); + viewController.onViewAttached(); + verify(mConfigurationController).addCallback(any()); + verify(mConfigurationController, never()).removeCallback(any()); + + viewController.onViewDetached(); + verify(mConfigurationController).removeCallback(any()); + } + private void setHaveFavorites(boolean value) { final List<StructureInfo> favorites = mock(List.class); when(favorites.isEmpty()).thenReturn(!value); diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt index e931384fd61e..65f68f9df3e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt @@ -22,6 +22,7 @@ import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater +import android.view.View import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -39,6 +40,8 @@ import com.android.systemui.util.time.FakeSystemClock import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.eq import org.mockito.Mockito.verify @@ -73,13 +76,20 @@ class ContrastDialogDelegateTest : SysuiTestCase() { if (Looper.myLooper() == null) Looper.prepare() mContrastDialogDelegate = - ContrastDialogDelegate( - sysuiDialogFactory, - mainExecutor, - mockUiModeManager, - mockUserTracker, - mockSecureSettings - ) + ContrastDialogDelegate( + sysuiDialogFactory, + mainExecutor, + mockUiModeManager, + mockUserTracker, + mockSecureSettings + ) + + mContrastDialogDelegate.createDialog() + val viewCaptor = ArgumentCaptor.forClass(View::class.java) + verify(sysuiDialog).setView(viewCaptor.capture()) + whenever(sysuiDialog.requireViewById(anyInt()) as View?).then { + viewCaptor.value.requireViewById(it.getArgument(0)) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt index 7750d25de753..ab6bc2ca2dda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt @@ -71,7 +71,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { VibrationEffect.startComposition() .addPrimitive( VibrationEffect.Composition.PRIMITIVE_CLICK, - scaleAtBookends(config.maxVelocityToScale) + sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale), ) .compose() @@ -86,7 +86,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { VibrationEffect.startComposition() .addPrimitive( VibrationEffect.Composition.PRIMITIVE_CLICK, - scaleAtBookends(config.maxVelocityToScale) + sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale) ) .compose() @@ -102,7 +102,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { VibrationEffect.startComposition() .addPrimitive( VibrationEffect.Composition.PRIMITIVE_CLICK, - scaleAtBookends(config.maxVelocityToScale) + sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale), ) .compose() @@ -117,7 +117,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { VibrationEffect.startComposition() .addPrimitive( VibrationEffect.Composition.PRIMITIVE_CLICK, - scaleAtBookends(config.maxVelocityToScale) + sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale), ) .compose() @@ -132,7 +132,11 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() { // GIVEN max velocity and slider progress val progress = 1f - val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress) + val expectedScale = + sliderHapticFeedbackProvider.scaleOnDragTexture( + config.maxVelocityToScale, + progress, + ) val ticks = VibrationEffect.startComposition() repeat(config.numberOfLowTicks) { ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale) @@ -203,7 +207,11 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() { // GIVEN max velocity and slider progress val progress = 1f - val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress) + val expectedScale = + sliderHapticFeedbackProvider.scaleOnDragTexture( + config.maxVelocityToScale, + progress, + ) val ticks = VibrationEffect.startComposition() repeat(config.numberOfLowTicks) { ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale) @@ -212,7 +220,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { VibrationEffect.startComposition() .addPrimitive( VibrationEffect.Composition.PRIMITIVE_CLICK, - scaleAtBookends(config.maxVelocityToScale) + sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale), ) .compose() @@ -232,7 +240,11 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() { // GIVEN max velocity and slider progress val progress = 1f - val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress) + val expectedScale = + sliderHapticFeedbackProvider.scaleOnDragTexture( + config.maxVelocityToScale, + progress, + ) val ticks = VibrationEffect.startComposition() repeat(config.numberOfLowTicks) { ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale) @@ -241,7 +253,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { VibrationEffect.startComposition() .addPrimitive( VibrationEffect.Composition.PRIMITIVE_CLICK, - scaleAtBookends(config.maxVelocityToScale) + sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale), ) .compose() @@ -289,28 +301,12 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() { assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress) } - private fun scaleAtBookends(velocity: Float): Float { - val range = config.upperBookendScale - config.lowerBookendScale - val interpolatedVelocity = - velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale) - return interpolatedVelocity * range + config.lowerBookendScale - } - - private fun scaleAtProgressChange(velocity: Float, progress: Float): Float { - val range = config.progressBasedDragMaxScale - config.progressBasedDragMinScale - val interpolatedVelocity = - velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale) - val interpolatedProgress = progressInterpolator.getInterpolation(progress) - val bump = interpolatedVelocity * config.additionalVelocityMaxBump - return interpolatedProgress * range + config.progressBasedDragMinScale + bump - } - private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect { val ticks = VibrationEffect.startComposition() repeat(config.numberOfLowTicks) { ticks.addPrimitive( VibrationEffect.Composition.PRIMITIVE_LOW_TICK, - scaleAtProgressChange(velocity, progress) + sliderHapticFeedbackProvider.scaleOnDragTexture(velocity, progress), ) } return ticks.compose() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 40c9432d543d..076d72513633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -53,9 +53,11 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -101,6 +103,8 @@ class CustomizationProviderTest : SysuiTestCase() { private lateinit var underTest: CustomizationProvider private lateinit var testScope: TestScope + private val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -185,6 +189,7 @@ class CustomizationProviderTest : SysuiTestCase() { }, ) .keyguardInteractor, + shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index d246f0e49e1c..4ef18cf1a15f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -93,10 +93,12 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.flags.SystemPropertiesHelper; +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.scene.FakeWindowRootViewComponent; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -189,6 +191,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock ShadeInteractor mShadeInteractor; private @Mock ShadeWindowLogger mShadeWindowLogger; private @Mock SelectedUserInteractor mSelectedUserInteractor; + private @Mock KeyguardInteractor mKeyguardInteractor; private @Captor ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback> @@ -214,6 +217,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock CoroutineDispatcher mDispatcher; private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; private @Mock SystemPropertiesHelper mSystemPropertiesHelper; + private @Mock SceneContainerFlags mSceneContainerFlags; private FakeFeatureFlags mFeatureFlags; private final int mDefaultUserId = 100; @@ -258,7 +262,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker); + mUserTracker, + mSceneContainerFlags); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true); @@ -1128,7 +1133,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mDreamingToLockscreenTransitionViewModel, mSystemPropertiesHelper, () -> mock(WindowManagerLockscreenVisibilityManager.class), - mSelectedUserInteractor); + mSelectedUserInteractor, + mKeyguardInteractor); mViewMediator.start(); mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index de12b8f91d20..6eb95bddaf53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -17,8 +17,10 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.trust.TrustManager import android.content.pm.UserInfo import android.hardware.biometrics.BiometricFaceConstants +import android.hardware.biometrics.BiometricSourceType import android.os.Handler import android.os.PowerManager import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -62,6 +64,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -74,8 +77,11 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -99,7 +105,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig - @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var trustManager: TrustManager @Before fun setup() { @@ -130,7 +137,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { mContext, testScope.backgroundScope, dispatcher, - dispatcher, faceAuthRepository, { PrimaryBouncerInteractor( @@ -146,7 +152,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, FakeTrustRepository(), testScope.backgroundScope, - mSelectedUserInteractor, + selectedUserInteractor, underTest, ) }, @@ -169,6 +175,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { faceWakeUpTriggersConfig, powerInteractor, fakeBiometricSettingsRepository, + trustManager, ) } @@ -498,6 +505,22 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { assertThat(faceAuthRepository.isLockedOut.value).isTrue() } + @Test + fun whenIsAuthenticatedFalse_clearFaceBiometrics() = + testScope.runTest { + underTest.start() + + faceAuthRepository.isAuthenticated.value = true + runCurrent() + verify(trustManager, never()) + .clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt()) + + faceAuthRepository.isAuthenticated.value = false + runCurrent() + + verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt()) + } + companion object { private const val primaryUserId = 1 private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 66c8a229f0a1..b4ae7e3a7ca9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -46,7 +46,9 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -242,6 +244,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var userTracker: UserTracker + private val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -311,6 +315,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { featureFlags = featureFlags, ) .keyguardInteractor, + shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 976dc5f01ff9..b8a8bdf06954 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -1137,7 +1137,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN primary bouncer shows - bouncerRepository.setPrimaryShow(true) // beverlyt + bouncerRepository.setPrimaryShow(true) runCurrent() val info = @@ -1232,6 +1232,36 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun dreamingToAod() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreaming(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN the device starts DOZE_AOD + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel( + from = DozeStateModel.INITIALIZED, + to = DozeStateModel.DOZE_AOD, + ) + ) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture()) + } + // THEN a transition to AOD should occur + assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING) + assertThat(info.to).isEqualTo(KeyguardState.AOD) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun lockscreenToOccluded() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 1584be0ab565..af38523c2fd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -54,9 +54,11 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -108,6 +110,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { private lateinit var dockManager: DockManagerFake private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + private val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -221,6 +225,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, + shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 0c30d10ea563..b6a661be8c74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -264,12 +264,14 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha) .thenReturn(emptyFlow()) whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow) + whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f)) underTest = KeyguardQuickAffordancesCombinedViewModel( quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, + shadeInteractor = shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, 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 58624d356e60..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 @@ -144,19 +144,50 @@ class KeyguardRootViewModelTest : SysuiTestCase() { @Test fun alpha() = testScope.runTest { - val value = collectLastValue(underTest.alpha) - assertThat(value()).isEqualTo(0f) + val alpha by collectLastValue(underTest.alpha) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.OFF, + to = KeyguardState.LOCKSCREEN, + testScope = testScope, + ) repository.setKeyguardAlpha(0.1f) - assertThat(value()).isEqualTo(0.1f) + assertThat(alpha).isEqualTo(0.1f) repository.setKeyguardAlpha(0.5f) - assertThat(value()).isEqualTo(0.5f) + assertThat(alpha).isEqualTo(0.5f) repository.setKeyguardAlpha(0.2f) - assertThat(value()).isEqualTo(0.2f) + assertThat(alpha).isEqualTo(0.2f) repository.setKeyguardAlpha(0f) - assertThat(value()).isEqualTo(0f) + assertThat(alpha).isEqualTo(0f) occludedToLockscreenAlpha.value = 0.8f - assertThat(value()).isEqualTo(0.8f) + assertThat(alpha).isEqualTo(0.8f) + } + + @Test + fun alphaWhenGoneEqualsZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, + ) + + repository.setKeyguardAlpha(0.1f) + assertThat(alpha).isEqualTo(0f) + repository.setKeyguardAlpha(0.5f) + assertThat(alpha).isEqualTo(0f) + repository.setKeyguardAlpha(1f) + assertThat(alpha).isEqualTo(0f) + } + + @Test + fun translationYInitialValueIsZero() = + testScope.runTest { + val translationY by collectLastValue(underTest.translationY) + assertThat(translationY).isEqualTo(0) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index 3bfdb84249ac..310e0b8e7ac3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -54,6 +54,7 @@ import com.android.systemui.statusbar.notification.collection.provider.OnReorder import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq @@ -124,6 +125,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Captor lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> private val clock = FakeSystemClock() + private lateinit var bgExecutor: FakeExecutor private lateinit var mediaCarouselController: MediaCarouselController @Before @@ -131,6 +133,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK)) transitionRepository = FakeKeyguardTransitionRepository() + bgExecutor = FakeExecutor(clock) mediaCarouselController = MediaCarouselController( context, @@ -140,6 +143,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { activityStarter, clock, executor, + bgExecutor, mediaDataManager, configurationController, falsingManager, @@ -458,6 +462,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaHostState, animate = false ) + bgExecutor.runAllReady() verify(logger).logCarouselPosition(LOCATION_QS) } @@ -468,6 +473,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaHostState, animate = false ) + bgExecutor.runAllReady() verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS) } @@ -478,6 +484,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaHostState, animate = false ) + bgExecutor.runAllReady() verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN) } @@ -488,6 +495,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaHostState, animate = false ) + bgExecutor.runAllReady() verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index 119ffd28bebe..ebd34de463f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -28,7 +28,6 @@ import android.os.UserManager import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import com.android.dx.mockito.inline.extended.ExtendedMockito -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -40,6 +39,7 @@ import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.C import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint import com.android.systemui.notetask.NoteTaskInfoResolver +import com.android.systemui.res.R import com.android.systemui.stylus.StylusManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -134,12 +134,9 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { // region lockScreenState @Test fun lockScreenState_stylusUsed_userUnlocked_isSelected_shouldEmitVisible() = runTest { - TestConfig() - .setStylusEverUsed(true) - .setUserUnlocked(true) - .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) - val underTest = createUnderTest() + TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(underTest) + val actual by collectLastValue(underTest.lockScreenState) assertThat(actual).isEqualTo(createLockScreenStateVisible()) @@ -148,10 +145,11 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Test fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() = runTest { + val underTest = createUnderTest() TestConfig() .setStylusEverUsed(true) .setUserUnlocked(true) - .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) + .setConfigSelections(underTest) whenever( roleManager.getRoleHoldersAsUser( eq(RoleManager.ROLE_NOTES), @@ -160,7 +158,6 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { ) .thenReturn(emptyList()) - val underTest = createUnderTest() val actual by collectLastValue(underTest.lockScreenState) assertThat(actual).isEqualTo(LockScreenState.Hidden) @@ -168,12 +165,9 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Test fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest { - TestConfig() - .setStylusEverUsed(false) - .setUserUnlocked(true) - .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) - val underTest = createUnderTest() + TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections(underTest) + val actual by collectLastValue(underTest.lockScreenState) assertThat(actual).isEqualTo(LockScreenState.Hidden) @@ -181,25 +175,22 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Test fun lockScreenState_stylusUsed_userLocked_isSelected_shouldEmitHidden() = runTest { - TestConfig() - .setStylusEverUsed(true) - .setUserUnlocked(false) - .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>()) - val underTest = createUnderTest() + TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections(underTest) + val actual by collectLastValue(underTest.lockScreenState) assertThat(actual).isEqualTo(LockScreenState.Hidden) } @Test - fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitVisible() = runTest { + fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitHidden() = runTest { TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections() val underTest = createUnderTest() val actual by collectLastValue(underTest.lockScreenState) - assertThat(actual).isEqualTo(createLockScreenStateVisible()) + assertThat(actual).isEqualTo(LockScreenState.Hidden) } @Test @@ -223,13 +214,13 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitVisible() = runTest { + fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitHidden() = runTest { TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(mock()) val underTest = createUnderTest() val actual by collectLastValue(underTest.lockScreenState) - assertThat(actual).isEqualTo(createLockScreenStateVisible()) + assertThat(actual).isEqualTo(LockScreenState.Hidden) } @Test 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/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt new file mode 100644 index 000000000000..e9714dc524ee --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -0,0 +1,155 @@ +/* + * 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.tiles + +import android.os.Handler +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.KeyguardDismissUtil +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.ArgumentMatchers.isA +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** + * This class tests the functionality of the RecordIssueTile. The initial state of the tile is + * always be inactive at the start of these tests. + */ +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class RecordIssueTileTest : SysuiTestCase() { + + @Mock private lateinit var host: QSHost + @Mock private lateinit var qsEventLogger: QsEventLogger + @Mock private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var qsLogger: QSLogger + @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator + @Mock private lateinit var dialogFactory: SystemUIDialog.Factory + @Mock private lateinit var dialog: SystemUIDialog + + private lateinit var testableLooper: TestableLooper + private lateinit var tile: RecordIssueTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(host.context).thenReturn(mContext) + whenever(dialogFactory.create(any())).thenReturn(dialog) + + testableLooper = TestableLooper.get(this) + tile = + RecordIssueTile( + host, + qsEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + FalsingManagerFake(), + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + keyguardDismissUtil, + keyguardStateController, + dialogLauncherAnimator, + dialogFactory + ) + } + + @Test + fun qsTileUi_shouldLookCorrect_whenInactive() { + tile.isRecording = false + + val testState = tile.newTileState() + tile.handleUpdateState(testState, null) + + assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE) + assertThat(testState.secondaryLabel.toString()) + .isEqualTo(mContext.getString(R.string.qs_record_issue_start)) + } + + @Test + fun qsTileUi_shouldLookCorrect_whenRecording() { + tile.isRecording = true + + val testState = tile.newTileState() + tile.handleUpdateState(testState, null) + + assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE) + assertThat(testState.secondaryLabel.toString()) + .isEqualTo(mContext.getString(R.string.qs_record_issue_stop)) + } + + @Test + fun inActiveQsTile_switchesToActive_whenClicked() { + tile.isRecording = false + + val testState = tile.newTileState() + tile.handleUpdateState(testState, null) + + assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE) + } + + @Test + fun activeQsTile_switchesToInActive_whenClicked() { + tile.isRecording = true + + val testState = tile.newTileState() + tile.handleUpdateState(testState, null) + + assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE) + } + + @Test + fun showPrompt_shouldUseKeyguardDismissUtil_ToShowDialog() { + tile.isRecording = false + tile.handleClick(null) + testableLooper.processAllMessages() + + verify(keyguardDismissUtil) + .executeWhenUnlocked( + isA(ActivityStarter.OnDismissAction::class.java), + eq(false), + eq(true) + ) + } +} 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/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt new file mode 100644 index 000000000000..bbc59d03ac2b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -0,0 +1,93 @@ +/* + * 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.recordissue + +import android.app.Dialog +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.widget.Button +import android.widget.Switch +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.model.SysUiState +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class RecordIssueDialogDelegateTest : SysuiTestCase() { + + private lateinit var dialog: SystemUIDialog + private lateinit var latch: CountDownLatch + + @Before + fun setup() { + val dialogFactory = + SystemUIDialog.Factory( + context, + mock<FeatureFlags>(), + mock<SystemUIDialogManager>(), + mock<SysUiState>().apply { + whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this) + }, + mock<BroadcastDispatcher>(), + mock<DialogLaunchAnimator>() + ) + + latch = CountDownLatch(1) + dialog = RecordIssueDialogDelegate(dialogFactory) { latch.countDown() }.createDialog() + dialog.show() + } + + @After + fun teardown() { + dialog.dismiss() + } + + @Test + fun dialog_hasCorrectUiElements_afterCreation() { + dialog.requireViewById<Switch>(R.id.screenrecord_switch) + dialog.requireViewById<Button>(R.id.issue_type_button) + + assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).text) + .isEqualTo(context.getString(R.string.qs_record_issue_start)) + assertThat(dialog.getButton(Dialog.BUTTON_NEGATIVE).text) + .isEqualTo(context.getString(R.string.cancel)) + } + + @Test + fun onStarted_isCalled_afterStartButtonIsClicked() { + dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick() + latch.await(1L, TimeUnit.MILLISECONDS) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java index 0f33aaf20195..2c7b60660165 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java @@ -56,6 +56,7 @@ import com.google.common.util.concurrent.Futures; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -138,6 +139,7 @@ public final class AppClipsActivityTest extends SysuiTestCase { } @Test + @Ignore("b/315848285") public void screenshotDisplayed_userConsented_screenshotExportedSuccessfully() { ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> { assertThat(resultCode).isEqualTo(RESULT_OK); 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 39739e7bb93d..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; @@ -75,6 +78,7 @@ import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.data.repository.FakeShadeRepository; @@ -138,6 +142,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private ShadeWindowLogger mShadeWindowLogger; @Mock private SelectedUserInteractor mSelectedUserInteractor; @Mock private UserTracker mUserTracker; + @Mock private SceneContainerFlags mSceneContainerFlags; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; @@ -233,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(), @@ -249,7 +259,9 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - new ResourcesSplitShadeStateController()), + new ResourcesSplitShadeStateController(), + keyguardInteractor, + deviceEntryUdfpsInteractor), shadeRepository ) ); @@ -274,7 +286,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker) { + mUserTracker, + mSceneContainerFlags) { @Override protected boolean isDebuggable() { return false; 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/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java index a3cff87e63b8..76c401547ce9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java @@ -30,8 +30,6 @@ import static org.mockito.Mockito.spy; import android.app.ActivityManager; import android.app.Notification; -import android.os.Handler; -import android.os.Looper; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; @@ -45,12 +43,12 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.FakeGlobalSettings; +import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; -import com.android.systemui.util.time.SystemClockImpl; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -73,27 +71,24 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800; // Number of notifications to use in tests requiring multiple notifications private static final int TEST_NUM_NOTIFICATIONS = 4; - protected static final int TEST_TIMEOUT_TIME = 2_000; - protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true; - protected Handler mTestHandler; protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); - protected final SystemClock mSystemClock = new SystemClockImpl(); - protected boolean mTimedOut = false; + protected final FakeSystemClock mSystemClock = new FakeSystemClock(); + protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock); @Mock protected ExpandableNotificationRow mRow; static { assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME); assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME); - assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME); } private static class TestableAlertingNotificationManager extends AlertingNotificationManager { private AlertEntry mLastCreatedEntry; - private TestableAlertingNotificationManager(Handler handler, SystemClock systemClock) { - super(new HeadsUpManagerLogger(logcatLogBuffer()), handler, systemClock); + private TestableAlertingNotificationManager(SystemClock systemClock, + DelayableExecutor executor) { + super(new HeadsUpManagerLogger(logcatLogBuffer()), systemClock, executor); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissTime = TEST_AUTO_DISMISS_TIME; mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME; @@ -118,7 +113,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { } protected AlertingNotificationManager createAlertingNotificationManager() { - return new TestableAlertingNotificationManager(mTestHandler, mSystemClock); + return new TestableAlertingNotificationManager(mSystemClock, mExecutor); } protected StatusBarNotification createSbn(int id, Notification n) { @@ -155,35 +150,6 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { return new NotificationEntryBuilder().setSbn(createSbn(id)).build(); } - protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry, - boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) { - final Boolean[] wasAlerting = {null}; - final Runnable checkAlerting = - () -> wasAlerting[0] = anm.isAlerting(entry.getKey()); - - mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis); - mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME); - TestableLooper.get(this).processMessages(2); - - assertFalse("Test timed out", mTimedOut); - if (shouldBeAlerting) { - assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]); - } else { - assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]); - } - assertFalse("Should not still be alerting after processing", - anm.isAlerting(entry.getKey())); - } - - @Before - public void setUp() { - mTestHandler = Handler.createAsync(Looper.myLooper()); - } - - @After - public void tearDown() { - mTestHandler.removeCallbacksAndMessages(null); - } @Test public void testShowNotification_addsEntry() { @@ -203,9 +169,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { final NotificationEntry entry = createEntry(/* id = */ 0); alm.showNotification(entry); - - verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2, - "auto dismiss time"); + mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2); assertFalse(alm.isAlerting(entry.getKey())); } @@ -217,10 +181,8 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { alm.showNotification(entry); - // Try to remove but defer, since the notification has not been shown long enough. - final boolean removedImmediately = alm.removeNotification(entry.getKey(), - false /* releaseImmediately */); - + final boolean removedImmediately = alm.removeNotification( + entry.getKey(), /* releaseImmediately = */ false); assertFalse(removedImmediately); assertTrue(alm.isAlerting(entry.getKey())); } @@ -232,10 +194,8 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { alm.showNotification(entry); - // Remove forcibly with releaseImmediately = true. - final boolean removedImmediately = alm.removeNotification(entry.getKey(), - true /* releaseImmediately */); - + final boolean removedImmediately = alm.removeNotification( + entry.getKey(), /* releaseImmediately = */ true); assertTrue(removedImmediately); assertFalse(alm.isAlerting(entry.getKey())); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 34c7b09ba86a..42c7375bfb2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -515,6 +515,29 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + public void testDevicePolicyDoesNotAllowNotifications_deviceOwnerSetsForUserAll() { + // User allows them + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + + // DevicePolicy hides notifs on lockscreen + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( + 0, null, null, 0, true, false, null, USER_ALL, 0); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test public void testDevicePolicyDoesNotAllowNotifications_userAll() { // User allows them mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); 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 85b9392732d6..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() @@ -413,7 +414,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val top = 123f val bottom = 456f keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) - assertThat(bounds).isEqualTo(NotificationContainerBounds(top, bottom)) + assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index 37ee32204f32..e1bd89fa7c34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; +import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -25,7 +26,6 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import android.content.Context; -import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -45,11 +45,11 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; -import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -88,9 +88,9 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { StatusBarStateController statusBarStateController, KeyguardBypassController keyguardBypassController, ConfigurationController configurationController, - Handler handler, GlobalSettings globalSettings, SystemClock systemClock, + DelayableExecutor executor, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, JavaAdapter javaAdapter, @@ -104,9 +104,10 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { groupManager, visualStabilityProvider, configurationController, - handler, + mockExecutorHandler(executor), globalSettings, systemClock, + executor, accessibilityManagerWrapper, uiEventLogger, javaAdapter, @@ -126,9 +127,9 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { mStatusBarStateController, mBypassController, mConfigurationController, - mTestHandler, mGlobalSettings, mSystemClock, + mExecutor, mAccessibilityManagerWrapper, mUiEventLogger, mJavaAdapter, @@ -142,7 +143,6 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { } @Before - @Override public void setUp() { when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); final AccessibilityManagerWrapper accessibilityMgr = @@ -153,14 +153,6 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { mDependency.injectMockDependency(NotificationShadeWindowController.class); mContext.getOrCreateTestableResources().addOverride( R.integer.ambient_notification_extension_time, 500); - - super.setUp(); - } - - @After - @Override - public void tearDown() { - super.tearDown(); } @Test @@ -224,8 +216,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { hmp.showNotification(entry); hmp.extendHeadsUp(); + mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2); - final int pastNormalTimeMillis = TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2; - verifyAlertingAtTime(hmp, entry, true, pastNormalTimeMillis, "normal time"); + assertTrue(hmp.isAlerting(entry.getKey())); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt index 5c5624692f07..84b2c4bfed34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt @@ -24,6 +24,7 @@ import android.view.DisplayCutout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.leak.RotationUtils import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE @@ -31,6 +32,7 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertTrue @@ -39,7 +41,6 @@ import org.junit.Test import org.mockito.ArgumentMatchers.any import org.mockito.Mock import org.mockito.Mockito.`when` -import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @SmallTest @@ -556,7 +557,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { fun testDisplayChanged_returnsUpdatedInsets() { // GIVEN: get insets on the first display and switch to the second display val provider = StatusBarContentInsetsProvider(contextMock, configurationController, - mock(DumpManager::class.java)) + mock<DumpManager>(), mock<CommandRegistry>()) configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160)) val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE) @@ -575,7 +576,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { // GIVEN: get insets on the first display, switch to the second display, // get insets and switch back val provider = StatusBarContentInsetsProvider(contextMock, configurationController, - mock(DumpManager::class.java)) + mock<DumpManager>(), mock<CommandRegistry>()) configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160)) val firstDisplayInsetsFirstCall = provider @@ -601,7 +602,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100) configurationController.onConfigurationChanged(configuration) val provider = StatusBarContentInsetsProvider(contextMock, configurationController, - mock(DumpManager::class.java)) + mock<DumpManager>(), mock<CommandRegistry>()) val listener = object : StatusBarContentInsetsChangedListener { var triggered = false @@ -623,7 +624,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { fun onDensityOrFontScaleChanged_listenerNotified() { configuration.densityDpi = 12 val provider = StatusBarContentInsetsProvider(contextMock, configurationController, - mock(DumpManager::class.java)) + mock<DumpManager>(), mock<CommandRegistry>()) val listener = object : StatusBarContentInsetsChangedListener { var triggered = false @@ -644,7 +645,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { @Test fun onThemeChanged_listenerNotified() { val provider = StatusBarContentInsetsProvider(contextMock, configurationController, - mock(DumpManager::class.java)) + mock<DumpManager>(), mock<CommandRegistry>()) val listener = object : StatusBarContentInsetsChangedListener { var triggered = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt index 22dce3a2ff64..1bdf64434fcb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -21,6 +21,7 @@ import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVIC import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text +import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -355,14 +356,14 @@ class InternetTileViewModelTest : SysuiTestCase() { connectivityRepository.setEthernetConnected(default = true, validated = true) - assertThat(latest?.secondaryLabel).isNull() - assertThat(latest?.secondaryTitle) - .isEqualTo(ethernetIcon!!.contentDescription.toString()) + assertThat(latest?.secondaryLabel.loadText(context)) + .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context)) + assertThat(latest?.secondaryTitle).isNull() assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully) assertThat(latest?.icon).isNull() assertThat(latest?.stateDescription).isNull() assertThat(latest?.contentDescription.loadContentDescription(context)) - .isEqualTo(latest?.secondaryTitle) + .isEqualTo(latest?.secondaryLabel.loadText(context)) } @Test @@ -373,14 +374,14 @@ class InternetTileViewModelTest : SysuiTestCase() { connectivityRepository.setEthernetConnected(default = true, validated = false) - assertThat(latest?.secondaryLabel).isNull() - assertThat(latest?.secondaryTitle) - .isEqualTo(ethernetIcon!!.contentDescription.toString()) + assertThat(latest?.secondaryLabel.loadText(context)) + .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context)) + assertThat(latest?.secondaryTitle).isNull() assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet) assertThat(latest?.icon).isNull() assertThat(latest?.stateDescription).isNull() assertThat(latest?.contentDescription.loadContentDescription(context)) - .isEqualTo(latest?.secondaryTitle) + .isEqualTo(latest?.secondaryLabel.loadText(context)) } private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 2940c398c315..4c893e3538a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.policy; import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED; import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer; +import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler; import static com.google.common.truth.Truth.assertThat; @@ -41,7 +42,6 @@ import android.app.Person; import android.content.Context; import android.content.Intent; import android.graphics.Region; -import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -56,11 +56,10 @@ import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; -import org.junit.After; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,7 +76,6 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200; private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000; - private static final int TEST_A11Y_TIMEOUT_TIME = 3_000; private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer())); @@ -87,25 +85,18 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME); assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME); assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME); - - assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan( - TEST_TIMEOUT_TIME); - assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan( - TEST_TIMEOUT_TIME); - assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan( - TEST_A11Y_TIMEOUT_TIME); } private final class TestableHeadsUpManager extends BaseHeadsUpManager { TestableHeadsUpManager(Context context, HeadsUpManagerLogger logger, - Handler handler, + DelayableExecutor executor, GlobalSettings globalSettings, SystemClock systemClock, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger) { - super(context, logger, handler, globalSettings, systemClock, - accessibilityManagerWrapper, uiEventLogger); + super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock, + executor, accessibilityManagerWrapper, uiEventLogger); mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME; mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissTime = TEST_AUTO_DISMISS_TIME; @@ -183,7 +174,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { } private BaseHeadsUpManager createHeadsUpManager() { - return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mGlobalSettings, + return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings, mSystemClock, mAccessibilityMgr, mUiEventLoggerFake); } @@ -234,18 +225,6 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { } - @Before - @Override - public void setUp() { - super.setUp(); - } - - @After - @Override - public void tearDown() { - super.tearDown(); - } - @Test public void testHunRemovedLogging() { final BaseHeadsUpManager hum = createHeadsUpManager(); @@ -305,10 +284,9 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { useAccessibilityTimeout(false); hum.showNotification(entry); + mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME); - final int pastJustAutoDismissMillis = - TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME; - verifyAlertingAtTime(hum, entry, true, pastJustAutoDismissMillis, "just auto dismiss"); + assertTrue(hum.isAlerting(entry.getKey())); } @@ -319,10 +297,10 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { useAccessibilityTimeout(false); hum.showNotification(entry); + mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2); - final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME - + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2; - verifyAlertingAtTime(hum, entry, false, pastDefaultTimeoutMillis, "default timeout"); + assertFalse(hum.isAlerting(entry.getKey())); } @@ -333,10 +311,10 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { useAccessibilityTimeout(false); hum.showNotification(entry); + mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2); - final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME - + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2; - verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout"); + assertTrue(hum.isAlerting(entry.getKey())); } @@ -347,18 +325,9 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { useAccessibilityTimeout(false); hum.showNotification(entry); + mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME); - final int pastLongestAutoDismissMillis = - TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME; - final Boolean[] wasAlerting = {null}; - final Runnable checkAlerting = - () -> wasAlerting[0] = hum.isAlerting(entry.getKey()); - mTestHandler.postDelayed(checkAlerting, pastLongestAutoDismissMillis); - TestableLooper.get(this).processMessages(1); - - assertTrue("Should still be alerting past longest auto-dismiss", wasAlerting[0]); - assertTrue("Should still be alerting after processing", - hum.isAlerting(entry.getKey())); + assertTrue(hum.isAlerting(entry.getKey())); } @@ -369,10 +338,10 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { useAccessibilityTimeout(true); hum.showNotification(entry); + mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2); - final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME - + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2; - verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout"); + assertTrue(hum.isAlerting(entry.getKey())); } @@ -383,10 +352,10 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { useAccessibilityTimeout(true); hum.showNotification(entry); + mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2); - final int pastStickyTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME - + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2; - verifyAlertingAtTime(hum, entry, true, pastStickyTimeoutMillis, "sticky timeout"); + assertTrue(hum.isAlerting(entry.getKey())); } @@ -398,18 +367,14 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { hum.showNotification(entry); - // Try to remove but defer, since the notification has not been shown long enough. final boolean removedImmediately = hum.removeNotification( - entry.getKey(), false /* releaseImmediately */); + entry.getKey(), /* releaseImmediately = */ false); + assertFalse(removedImmediately); + assertTrue(hum.isAlerting(entry.getKey())); - assertFalse("HUN should not be removed before minimum display time", removedImmediately); - assertTrue("HUN should still be alerting before minimum display time", - hum.isAlerting(entry.getKey())); + mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2); - final int pastMinimumDisplayTimeMillis = - (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2; - verifyAlertingAtTime(hum, entry, false, pastMinimumDisplayTimeMillis, - "minimum display time"); + assertFalse(hum.isAlerting(entry.getKey())); } @@ -420,32 +385,13 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { useAccessibilityTimeout(false); hum.showNotification(entry); + mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2); + + assertTrue(hum.isAlerting(entry.getKey())); - // After the minimum display time: - // 1. Check whether the notification is still alerting. - // 2. Try to remove it and check whether the remove succeeded. - // 3. Check whether it is still alerting after trying to remove it. - final Boolean[] livedPastMinimumDisplayTime = {null}; - final Boolean[] removedAfterMinimumDisplayTime = {null}; - final Boolean[] livedPastRemoveAfterMinimumDisplayTime = {null}; - final Runnable pastMinimumDisplayTimeRunnable = () -> { - livedPastMinimumDisplayTime[0] = hum.isAlerting(entry.getKey()); - removedAfterMinimumDisplayTime[0] = hum.removeNotification( - entry.getKey(), /* releaseImmediately = */ false); - livedPastRemoveAfterMinimumDisplayTime[0] = hum.isAlerting(entry.getKey()); - }; - final int pastMinimumDisplayTimeMillis = - (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2; - mTestHandler.postDelayed(pastMinimumDisplayTimeRunnable, pastMinimumDisplayTimeMillis); - // Wait until the minimum display time has passed before attempting removal. - TestableLooper.get(this).processMessages(1); - - assertTrue("HUN should live past minimum display time", - livedPastMinimumDisplayTime[0]); - assertTrue("HUN should be removed immediately past minimum display time", - removedAfterMinimumDisplayTime[0]); - assertFalse("HUN should not live after being removed past minimum display time", - livedPastRemoveAfterMinimumDisplayTime[0]); + final boolean removedImmediately = hum.removeNotification( + entry.getKey(), /* releaseImmediately = */ false); + assertTrue(removedImmediately); assertFalse(hum.isAlerting(entry.getKey())); } @@ -457,10 +403,8 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { hum.showNotification(entry); - // Remove forcibly with releaseImmediately = true. final boolean removedImmediately = hum.removeNotification( entry.getKey(), /* releaseImmediately = */ true); - assertTrue(removedImmediately); assertFalse(hum.isAlerting(entry.getKey())); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java index 01dad381efa0..479309c18c92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -153,6 +153,36 @@ public class KeyguardStateControllerTest extends SysuiTestCase { } @Test + public void testCanSkipLockScreen_updateCalledOnFacesCleared() { + verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture()); + + // Cannot skip after there's a password/pin/pattern + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); + ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */); + assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse(); + + // Unless user is authenticated + when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true); + mUpdateCallbackCaptor.getValue().onFacesCleared(); + assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue(); + } + + @Test + public void testCanSkipLockScreen_updateCalledOnFingerprintssCleared() { + verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture()); + + // Cannot skip after there's a password/pin/pattern + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); + ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */); + assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse(); + + // Unless user is authenticated + when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true); + mUpdateCallbackCaptor.getValue().onFingerprintsCleared(); + assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue(); + } + + @Test public void testIsUnlocked() { // Is unlocked whenever the keyguard is not showing assertThat(mKeyguardStateController.isShowing()).isFalse(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 112368895888..b58a41c89a4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.theme; +import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; @@ -90,6 +91,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { private static final int USER_SYSTEM = UserHandle.USER_SYSTEM; private static final int USER_SECONDARY = 10; + private static final UserHandle MANAGED_USER_HANDLE = UserHandle.of(100); + private static final UserHandle PRIVATE_USER_HANDLE = UserHandle.of(101); + @Mock private JavaAdapter mJavaAdapter; @Mock @@ -174,6 +178,14 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { Integer.toHexString(mColorScheme.getSeed() | 0xff000000))); return overlay; } + + @VisibleForTesting + protected boolean isPrivateProfile(UserHandle userHandle) { + if (userHandle.getIdentifier() == PRIVATE_USER_HANDLE.getIdentifier()) { + return true; + } + return false; + } }; mWakefulnessLifecycle.dispatchFinishedWakingUp(); @@ -675,7 +687,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { @Test public void onProfileAdded_setsTheme() { mBroadcastReceiver.getValue().onReceive(null, - new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); + new Intent(Intent.ACTION_PROFILE_ADDED) + .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } @@ -684,7 +697,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { reset(mDeviceProvisionedController); when(mUserManager.isManagedProfile(anyInt())).thenReturn(false); mBroadcastReceiver.getValue().onReceive(null, - new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); + new Intent(Intent.ACTION_PROFILE_ADDED) + .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); verify(mThemeOverlayApplier) .applyCurrentUserOverlays(any(), any(), anyInt(), any()); } @@ -694,11 +708,25 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { reset(mDeviceProvisionedController); when(mUserManager.isManagedProfile(anyInt())).thenReturn(true); mBroadcastReceiver.getValue().onReceive(null, - new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED)); + new Intent(Intent.ACTION_PROFILE_ADDED) + .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE)); + verify(mThemeOverlayApplier, never()) + .applyCurrentUserOverlays(any(), any(), anyInt(), any()); + } + + @Test + public void onPrivateProfileAdded_ignoresUntilStartComplete() { + mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + reset(mDeviceProvisionedController); + when(mUserManager.isManagedProfile(anyInt())).thenReturn(false); + mBroadcastReceiver.getValue().onReceive(null, + (new Intent(Intent.ACTION_PROFILE_ADDED)) + .putExtra(Intent.EXTRA_USER, PRIVATE_USER_HANDLE)); verify(mThemeOverlayApplier, never()) .applyCurrentUserOverlays(any(), any(), anyInt(), any()); } + @Test public void onWallpaperColorsChanged_firstEventBeforeUserSetup_shouldBeAccepted() { // By default, on setup() we make this controller return that the user finished setup 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 8585d46fa8a5..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; @@ -119,6 +122,7 @@ import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; @@ -343,6 +347,8 @@ public class BubblesTest extends SysuiTestCase { private Icon mAppBubbleIcon; @Mock private Display mDefaultDisplay; + @Mock + private SceneContainerFlags mSceneContainerFlags; private final SceneTestUtils mUtils = new SceneTestUtils(this); private final TestScope mTestScope = mUtils.getTestScope(); @@ -462,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(), @@ -478,7 +488,9 @@ public class BubblesTest extends SysuiTestCase { new SharedNotificationContainerInteractor( configurationRepository, mContext, - splitShadeStateController), + splitShadeStateController, + keyguardInteractor, + deviceEntryUdfpsInteractor), shadeRepository ) ); @@ -503,7 +515,8 @@ public class BubblesTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker + mUserTracker, + mSceneContainerFlags ); mNotificationShadeWindowController.fetchWindowRootView(); mNotificationShadeWindowController.attach(); 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/accessibility/data/repository/FakeColorCorrectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorCorrectionRepository.kt new file mode 100644 index 000000000000..607a4f3e1c0f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorCorrectionRepository.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.accessibility.data.repository + +import android.os.UserHandle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeColorCorrectionRepository : ColorCorrectionRepository { + private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>() + + override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> { + return getFlow(userHandle.identifier) + } + + override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean { + getFlow(userHandle.identifier).value = isEnabled + return true + } + + /** initializes the flow if already not */ + private fun getFlow(userId: Int): MutableStateFlow<Boolean> { + return userMap.getOrPut(userId) { MutableStateFlow(false) } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index 4642b470fd2d..7c5696c716d8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -20,10 +20,10 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel.SecurityMode +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationResultModel -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module @@ -47,10 +47,9 @@ class FakeAuthenticationRepository( private val _isPatternVisible = MutableStateFlow(true) override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow() - override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> = - MutableStateFlow(null) + override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null) - override val hasThrottlingOccurred = MutableStateFlow(false) + override val hasLockoutOccurred = MutableStateFlow(false) private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false) override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = @@ -70,10 +69,12 @@ class FakeAuthenticationRepository( _isPinEnhancedPrivacyEnabled.asStateFlow() private var failedAttemptCount = 0 - private var throttlingEndTimestamp = 0L + private var lockoutEndTimestamp = 0L private var credentialOverride: List<Any>? = null private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode() + var lockoutStartedReportCount = 0 + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return authenticationMethod.value } @@ -92,6 +93,10 @@ class FakeAuthenticationRepository( authenticationChallengeResult.emit(isSuccessful) } + override suspend fun reportLockoutStarted(durationMs: Int) { + lockoutStartedReportCount++ + } + override suspend fun getPinLength(): Int { return (credentialOverride ?: DEFAULT_PIN).size } @@ -100,18 +105,18 @@ class FakeAuthenticationRepository( return failedAttemptCount } - override suspend fun getThrottlingEndTimestamp(): Long { - return throttlingEndTimestamp + override suspend fun getLockoutEndTimestamp(): Long { + return lockoutEndTimestamp } fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) { _isAutoConfirmFeatureEnabled.value = isEnabled } - override suspend fun setThrottleDuration(durationMs: Int) { - throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 + override suspend fun setLockoutDuration(durationMs: Int) { + lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 if (durationMs > 0) { - hasThrottlingOccurred.value = true + hasLockoutOccurred.value = true } } @@ -131,18 +136,16 @@ class FakeAuthenticationRepository( else -> error("Unexpected credential type ${credential.type}!") } - return if ( - isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1 - ) { - hasThrottlingOccurred.value = false + return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { + hasLockoutOccurred.value = false AuthenticationResultModel( isSuccessful = isSuccessful, - throttleDurationMs = 0, + lockoutDurationMs = 0, ) } else { AuthenticationResultModel( isSuccessful = false, - throttleDurationMs = THROTTLE_DURATION_MS, + lockoutDurationMs = LOCKOUT_DURATION_MS, ) } } @@ -172,9 +175,9 @@ class FakeAuthenticationRepository( AuthenticationPatternCoordinate(0, 1), AuthenticationPatternCoordinate(0, 2), ) - const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5 - const val THROTTLE_DURATION_SECONDS = 30 - const val THROTTLE_DURATION_MS = THROTTLE_DURATION_SECONDS * 1000 + const val MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT = 5 + const val LOCKOUT_DURATION_SECONDS = 30 + const val LOCKOUT_DURATION_MS = LOCKOUT_DURATION_SECONDS * 1000 const val HINTING_PIN_LENGTH = 6 val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } } 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/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index e289083a2d9e..a1b6587be0b8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.filterNotNull @SysUISingleton class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository { - override val isAuthenticated = MutableStateFlow(false) override val canRunFaceAuth = MutableStateFlow(false) private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null) 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..4200f05ad64b 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 @@ -41,6 +41,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _deferKeyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow() override val keyguardDone: Flow<KeyguardDone> = _deferKeyguardDone + private val _keyguardDoneAnimationsFinished: MutableSharedFlow<Unit> = + MutableSharedFlow(extraBufferCapacity = 1) + override val keyguardDoneAnimationsFinished: Flow<Unit> = _keyguardDoneAnimationsFinished + private val _clockShouldBeCentered = MutableStateFlow<Boolean>(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered @@ -121,6 +125,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 } @@ -174,6 +180,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _deferKeyguardDone.emit(timing) } + override fun keyguardDoneAnimationsFinished() { + _keyguardDoneAnimationsFinished.tryEmit(Unit) + } + override fun setClockShouldBeCentered(shouldBeCentered: Boolean) { _clockShouldBeCentered.value = shouldBeCentered } 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/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.kt new file mode 100644 index 000000000000..0357036d907c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.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.tiles.impl.colorcorrection + +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger + +val Kosmos.qsColorCorrectionTileConfig by + Kosmos.Fixture { QSAccessibilityModule.provideColorCorrectionTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt new file mode 100644 index 000000000000..e9a394aef6de --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.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.tiles.impl.saver + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.connectivity.ConnectivityModule + +val Kosmos.qsDataSaverTileConfig by + Kosmos.Fixture { ConnectivityModule.provideDataSaverTileConfig(qsEventLogger) } 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/smartspace/data/repository/FakeSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt index c8013ef96fa7..862e52d7703f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt @@ -10,12 +10,12 @@ class FakeSmartspaceRepository( override val isSmartspaceRemoteViewsEnabled = smartspaceRemoteViewsEnabled - private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = + private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> = MutableStateFlow(emptyList()) - override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> = - _lockscreenSmartspaceTargets + override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> = + _communalSmartspaceTargets - fun setLockscreenSmartspaceTargets(targets: List<SmartspaceTarget>) { - _lockscreenSmartspaceTargets.value = targets + fun setCommunalSmartspaceTargets(targets: List<SmartspaceTarget>) { + _communalSmartspaceTargets.value = targets } } 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/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java index 886722e46376..bade84890f41 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java @@ -19,19 +19,38 @@ import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; +import java.util.ArrayList; +import java.util.List; + public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController { + private boolean mIsEnabled = false; + private List<Listener> mListeners = new ArrayList<>(); + public FakeDataSaverController(LeakCheck test) { super(test, "datasaver"); } @Override public boolean isDataSaverEnabled() { - return false; + return mIsEnabled; } @Override public void setDataSaverEnabled(boolean enabled) { + mIsEnabled = enabled; + for (Listener listener: mListeners) { + listener.onDataSaverChanged(enabled); + } + } + @Override + public void addCallback(Listener listener) { + mListeners.add(listener); + } + + @Override + public void removeCallback(Listener listener) { + mListeners.remove(listener); } } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index b315f4a0f0c5..7fcef9c90812 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -76,6 +76,7 @@ import android.util.Size; import android.view.Surface; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl; import androidx.camera.extensions.impl.AutoPreviewExtenderImpl; import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl; @@ -94,6 +95,7 @@ import androidx.camera.extensions.impl.PreviewExtenderImpl; import androidx.camera.extensions.impl.PreviewExtenderImpl.ProcessorType; import androidx.camera.extensions.impl.PreviewImageProcessorImpl; import androidx.camera.extensions.impl.ProcessResultImpl; +import androidx.camera.extensions.impl.ProcessorImpl; import androidx.camera.extensions.impl.RequestUpdateProcessorImpl; import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.AutoAdvancedExtenderImpl; @@ -101,6 +103,7 @@ import androidx.camera.extensions.impl.advanced.BeautyAdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.BokehAdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl; import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl; +import androidx.camera.extensions.impl.advanced.EyesFreeVideographyAdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.HdrAdvancedExtenderImpl; import androidx.camera.extensions.impl.advanced.ImageProcessorImpl; import androidx.camera.extensions.impl.advanced.ImageReaderOutputConfigImpl; @@ -112,6 +115,8 @@ import androidx.camera.extensions.impl.advanced.RequestProcessorImpl; import androidx.camera.extensions.impl.advanced.SessionProcessorImpl; import androidx.camera.extensions.impl.advanced.SurfaceOutputConfigImpl; +import com.android.internal.camera.flags.Flags; + import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -135,22 +140,28 @@ public class CameraExtensionsProxyService extends Service { private static final String RESULTS_VERSION_PREFIX = "1.3"; // Support for various latency improvements private static final String LATENCY_VERSION_PREFIX = "1.4"; - private static final String[] ADVANCED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX, - ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX }; - private static final String[] SUPPORTED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX, - RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX}; + private static final String EFV_VERSION_PREFIX = "1.5"; + private static final String[] ADVANCED_VERSION_PREFIXES = {EFV_VERSION_PREFIX, + LATENCY_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX }; + private static final String[] SUPPORTED_VERSION_PREFIXES = {EFV_VERSION_PREFIX, + LATENCY_VERSION_PREFIX, RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1", + NON_INIT_VERSION_PREFIX}; private static final boolean EXTENSIONS_PRESENT = checkForExtensions(); private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ? (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null; private static final boolean ESTIMATED_LATENCY_API_SUPPORTED = checkForLatencyAPI(); private static final boolean LATENCY_IMPROVEMENTS_SUPPORTED = EXTENSIONS_PRESENT && - (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX)); + (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) || + (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX))); + private static final boolean EFV_SUPPORTED = EXTENSIONS_PRESENT && + (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX)); private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI(); private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT && (!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX)); private static final boolean RESULT_API_SUPPORTED = EXTENSIONS_PRESENT && (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX) || - EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX)); + EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) || + EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX)); private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>(); private CameraManager mCameraManager; @@ -509,6 +520,167 @@ public class CameraExtensionsProxyService extends Service { */ public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension( int extensionType) { + if (Flags.concertMode()) { + if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) { + // Basic extensions are deprecated starting with extension version 1.5 + return new Pair<>(new PreviewExtenderImpl() { + @Override + public boolean isExtensionAvailable(String cameraId, + CameraCharacteristics cameraCharacteristics) { + return false; + } + + @Override + public void init(String cameraId, CameraCharacteristics cameraCharacteristics) { + + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl getCaptureStage() { + return null; + } + + @Override + public ProcessorType getProcessorType() { + return null; + } + + @Override + public ProcessorImpl getProcessor() { + return null; + } + + @Nullable + @Override + public List<Pair<Integer, Size[]>> getSupportedResolutions() { + return null; + } + + @Override + public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics, + Context context) { } + + @Override + public void onDeInit() { } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() { + return null; + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() { + return null; + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() { + return null; + } + + @Override + public int onSessionType() { + return 0; + } + }, new ImageCaptureExtenderImpl() { + @Override + public boolean isExtensionAvailable(String cameraId, + CameraCharacteristics cameraCharacteristics) { + return false; + } + + @Override + public void init(String cameraId, + CameraCharacteristics cameraCharacteristics) { } + + @Override + public CaptureProcessorImpl getCaptureProcessor() { + return null; + } + + @Override + public + List<androidx.camera.extensions.impl.CaptureStageImpl> getCaptureStages() { + return null; + } + + @Override + public int getMaxCaptureStage() { + return 0; + } + + @Override + public List<Pair<Integer, Size[]>> getSupportedResolutions() { + return null; + } + + @Override + public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions( + Size captureSize) { + return null; + } + + @Override + public Range<Long> getEstimatedCaptureLatencyRange( + Size captureOutputSize) { + return null; + } + + @Override + public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() { + return null; + } + + @Override + public List<CaptureResult.Key> getAvailableCaptureResultKeys() { + return null; + } + + @Override + public boolean isCaptureProcessProgressAvailable() { + return false; + } + + @Override + public Pair<Long, Long> getRealtimeCaptureLatency() { + return null; + } + + @Override + public boolean isPostviewAvailable() { + return false; + } + + @Override + public void onInit(String cameraId, + CameraCharacteristics cameraCharacteristics, Context context) { } + + @Override + public void onDeInit() { } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() { + return null; + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() { + return null; + } + + @Override + public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() { + return null; + } + + @Override + public int onSessionType() { + return 0; + } + }); + } + } + switch (extensionType) { case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC: return new Pair<>(new AutoPreviewExtenderImpl(), @@ -533,6 +705,82 @@ public class CameraExtensionsProxyService extends Service { * @hide */ public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) { + if (Flags.concertMode()) { + if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) { + if (EFV_SUPPORTED) { + return new EyesFreeVideographyAdvancedExtenderImpl(); + } else { + return new AdvancedExtenderImpl() { + @Override + public boolean isExtensionAvailable(String cameraId, + Map<String, CameraCharacteristics> characteristicsMap) { + return false; + } + + @Override + public void init(String cameraId, + Map<String, CameraCharacteristics> characteristicsMap) { + + } + + @Override + public Range<Long> getEstimatedCaptureLatencyRange(String cameraId, + Size captureOutputSize, int imageFormat) { + return null; + } + + @Override + public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions( + String cameraId) { + return null; + } + + @Override + public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions( + String cameraId) { + return null; + } + + @Override + public Map<Integer, List<Size>> getSupportedPostviewResolutions( + Size captureSize) { + return null; + } + + @Override + public List<Size> getSupportedYuvAnalysisResolutions(String cameraId) { + return null; + } + + @Override + public SessionProcessorImpl createSessionProcessor() { + return null; + } + + @Override + public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() { + return null; + } + + @Override + public List<CaptureResult.Key> getAvailableCaptureResultKeys() { + return null; + } + + @Override + public boolean isCaptureProcessProgressAvailable() { + return false; + } + + @Override + public boolean isPostviewAvailable() { + return false; + } + }; + } + } + } + switch (extensionType) { case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC: return new AutoAdvancedExtenderImpl(); diff --git a/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md index 30e899cd3ce3..d84cb6795fef 100644 --- a/ravenwood/api-maintainers.md +++ b/ravenwood/api-maintainers.md @@ -71,3 +71,24 @@ public class MyStruct { The “replace” strategy described above is quite powerful, and can be used in creative ways to sidestep tricky underlying dependencies that aren’t ready yet. For example, consider a constructor or static initializer that relies on unsupported functionality from another team. By factoring the unsupported logic into a dedicated method, that method can then be replaced under Ravenwood to offer baseline functionality. + +## Strategies for JNI + +At the moment, JNI isn't yet supported under Ravenwood, but you may still want to support APIs that are partially implemented with JNI. The current approach is to use the “replace” strategy to offer a pure-Java alternative implementation for any JNI-provided logic. + +Since this approach requires potentially complex re-implementation, it should only be considered for core infrastructure that is critical to unblocking widespread testing use-cases. Other less-common usages of JNI should instead wait for offical JNI support in the Ravenwood environment. + +When a pure-Java implementation grows too large or complex to host within the original class, the `@RavenwoodNativeSubstitutionClass` annotation can be used to host it in a separate source file: + +``` +@RavenwoodKeepWholeClass +@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.MyComplexClass_host") +public class MyComplexClass { + private static native void nativeDoThing(long nativePtr); +... + +public class MyComplexClass_host { + public static void nativeDoThing(long nativePtr) { + // ... + } +``` 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 e3f1932316da..13908f1732e1 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -1,6 +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 @@ -22,8 +32,16 @@ android.util.SparseSetArray 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 @@ -33,12 +51,15 @@ android.os.IBinder android.os.Looper android.os.Message android.os.MessageQueue +android.os.PackageTagsList android.os.Parcel android.os.Parcelable android.os.Process android.os.SystemClock android.os.ThreadLocalWorkSource +android.os.TimestampedValue android.os.UserHandle +android.os.WorkSource android.content.ClipData android.content.ClipData$Item @@ -92,8 +113,6 @@ android.content.ContentProvider com.android.server.LocalServices -com.android.internal.os.SomeArgs - com.android.internal.util.BitUtils com.android.internal.util.BitwiseInputStream com.android.internal.util.BitwiseOutputStream @@ -115,6 +134,23 @@ com.android.internal.util.QuickSelect com.android.internal.util.RingBuffer com.android.internal.util.StringPool +com.android.internal.os.BinderCallHeavyHitterWatcher +com.android.internal.os.BinderDeathDispatcher +com.android.internal.os.BinderfsStatsReader +com.android.internal.os.BinderLatencyBuckets +com.android.internal.os.CachedDeviceState +com.android.internal.os.Clock +com.android.internal.os.CpuScalingPolicies +com.android.internal.os.CpuScalingPolicyReader +com.android.internal.os.KernelCpuThreadReader +com.android.internal.os.LoggingPrintStream +com.android.internal.os.LooperStats +com.android.internal.os.MonotonicClock +com.android.internal.os.ProcLocksReader +com.android.internal.os.ProcStatsUtil +com.android.internal.os.SomeArgs +com.android.internal.os.StoragedUidIoStatsReader + com.google.android.collect.Lists com.google.android.collect.Maps com.google.android.collect.Sets diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md index de05777f01cd..5df827f11033 100644 --- a/ravenwood/test-authors.md +++ b/ravenwood/test-authors.md @@ -74,7 +74,7 @@ Once you’ve defined your test, you can use typical commands to execute it loca $ atest --host MyTestsRavenwood ``` -> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. Using the `--host` argument above is a way to bypass this requirement until bug #312525698 is fixed. +> **Note:** There's a known bug #312525698 where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. Using the `--host` argument above is a way to bypass this requirement until the bug is fixed. You can also run your new tests automatically via `TEST_MAPPING` rules like this: @@ -89,6 +89,8 @@ You can also run your new tests automatically via `TEST_MAPPING` rules like this } ``` +> **Note:** There's a known bug #308854804 where `TEST_MAPPING` is not being applied, so we're currently planning to run all Ravenwood tests unconditionally in presubmit for changes to `frameworks/base/` and `cts/` until there is a better path forward. + ## Strategies for feature flags Ravenwood supports writing tests against logic that uses feature flags through the existing `SetFlagsRule` infrastructure maintained by the feature flagging team: @@ -112,9 +114,9 @@ This naturally composes together well with any `RavenwoodRule` that your test ma ## Strategies for migration/bivalent tests -Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can run on both a real Android device and under a Ravenwood environment. +Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment. -In situations where a test method depends on API functionality not yet available under Ravenwood, we provide an annotation to quietly “ignore” that test under Ravenwood, while continuing to validate that test on real devices. Please note that your test must declare a `RavenwoodRule` for the annotation to take effect. +In situations where a test method depends on API functionality not yet available under Ravenwood, we provide an annotation to quietly “ignore” that test under Ravenwood, while continuing to validate that test on real devices. The annotation can be applied to either individual methods or to an entire test class. Please note that your test class must declare a `RavenwoodRule` for the annotation to take effect. Test authors are encouraged to provide a `blockedBy` or `reason` argument to help future maintainers understand why a test is being ignored, and under what conditions it might be supported in the future. @@ -137,11 +139,39 @@ public class MyCodeTest { } ``` +At the moment, the `android.content.res.Resources` subsystem isn't yet supported under Ravenwood, but you may still want to dual-compile test suites that depend on references to resources. Below is a strategy for supporting dual-compiliation, where you can "borrow" the generated resource symbols from your traditional `android_test` target: + +``` +android_test { + name: "MyTestsDevice", + resource_dirs: ["res"], +... + +android_ravenwood_test { + name: "MyTestsRavenwood", + srcs: [ + ":MyTestsDevice{.aapt.srcjar}", +... +``` + ## Strategies for unsupported APIs As you write tests against Ravenwood, you’ll likely discover API dependencies that aren’t supported yet. Here’s a few strategies that can help you make progress: -* Your code-under-test may benefit from subtle dependency refactoring to reduce coupling. (For example, providing a specific `File` argument instead of deriving it internally from a `Context`.) +* Your code-under-test may benefit from subtle dependency refactoring to reduce coupling. (For example, providing a specific `File` argument instead of deriving paths internally from a `Context` or `Environment`.) + * One common use-case is providing a directory for your test to store temporary files, which can easily be accomplished using the `Files.createTempDirectory()` API which works on both physical devices and under Ravenwood: + +``` +import java.nio.file.Files; + +@RunWith(AndroidJUnit4.class) +public class MyTest { + @Before + public void setUp() throws Exception { + File tempDir = Files.createTempDirectory("MyTest").toFile(); +... +``` + * Although mocking code that your team doesn’t own is a generally discouraged testing practice, it can be a valuable pressure relief valve when a dependency isn’t yet supported. ## Strategies for debugging test development diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp index e9bb763e143a..e2488a51bba1 100644 --- a/services/accessibility/Android.bp +++ b/services/accessibility/Android.bp @@ -19,6 +19,9 @@ java_library_static { defaults: [ "platform_service_defaults", ], + lint: { + error_checks: ["MissingPermissionAnnotation"], + }, srcs: [ ":services.accessibility-sources", "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc", @@ -32,6 +35,22 @@ java_library_static { ], } +java_library_static { + name: "AccessibilityGestureUtils", + srcs: [ + "java/**/gestures/GestureMatcher.java", + "java/**/gestures/GestureManifold.java", + "java/**/gestures/MultiFingerMultiTap.java", + "java/**/gestures/TouchState.java", + ], + static_libs: [ + "services.accessibility", + ], + libs: [ + "androidx.annotation_annotation", + ], +} + aconfig_declarations { name: "com_android_server_accessibility_flags", package: "com.android.server.accessibility", diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 0696807b3c8c..1d73843260cb 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -118,6 +118,7 @@ import java.util.Set; * This class represents an accessibility client - either an AccessibilityService or a UiAutomation. * It is responsible for behavior common to both types of clients. */ +@SuppressWarnings("MissingPermissionAnnotation") abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter, FingerprintGestureDispatcher.FingerprintGestureClient { @@ -209,6 +210,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 +399,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 +1614,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 +1934,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..abcd8e2a2d7e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -57,6 +57,7 @@ import java.util.StringJoiner; * * NOTE: This class has to be created and poked only from the main thread. */ +@SuppressWarnings("MissingPermissionAnnotation") class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation { private static final String TAG = AccessibilityInputFilter.class.getSimpleName(); @@ -198,6 +199,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 +527,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 +561,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 +941,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 +970,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..3d8d7b738233 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -195,6 +195,7 @@ import java.util.function.Predicate; * event dispatch for {@link AccessibilityEvent}s generated across all processes * on the device. Events are dispatched to {@link AccessibilityService}s. */ +@SuppressWarnings("MissingPermissionAnnotation") public class AccessibilityManagerService extends IAccessibilityManager.Stub implements AbstractAccessibilityServiceConnection.SystemSupport, AccessibilityUserState.ServiceInfoChangeListener, @@ -2825,8 +2826,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 +2848,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/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 40ca694dddd8..5ebe16115a4b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -65,6 +65,7 @@ import java.util.Set; * passed to the service it represents as soon it is bound. It also serves as the * connection for the service. */ +@SuppressWarnings("MissingPermissionAnnotation") class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection { private static final String LOG_TAG = "AccessibilityServiceConnection"; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java index 307b555b3b99..8471061358dd 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java @@ -224,7 +224,7 @@ public class AccessibilityTraceManager implements AccessibilityTrace { pw.println(" IAccessibilityInteractionConnectionCallback"); pw.println(" IRemoteMagnificationAnimationCallback"); pw.println(" IMagnificationConnection"); - pw.println(" IWindowMagnificationConnectionCallback"); + pw.println(" IMagnificationConnectionCallback"); pw.println(" WindowManagerInternal"); pw.println(" WindowsForAccessibilityCallback"); pw.println(" MagnificationCallbacks"); diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java index a525e7c64bc3..b119d7d117cd 100644 --- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java +++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java @@ -34,6 +34,7 @@ import java.util.List; * If we are stripping and/or replacing the actions from a window, we need to intercept the * nodes heading back to the service and swap out the actions. */ +@SuppressWarnings("MissingPermissionAnnotation") public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub { private static final boolean DEBUG = false; private static final String LOG_TAG = "ActionReplacingCallback"; diff --git a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java index c9ec16edc54e..e10e87c51d59 100644 --- a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java @@ -33,6 +33,7 @@ import java.util.List; /** * Encapsulate fingerprint gesture logic */ +@SuppressWarnings("MissingPermissionAnnotation") public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallback.Stub implements Handler.Callback{ private static final int MSG_REGISTER = 1; diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java index ab01fc324ed2..6aa4702ec7d5 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java @@ -64,6 +64,7 @@ import java.util.Set; * * TODO(241429275): Initialize this when a proxy is registered. */ +@SuppressWarnings("MissingPermissionAnnotation") public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection { private static final String LOG_TAG = "ProxyAccessibilityServiceConnection"; diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 53c629a9ed2d..f69104db7c10 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -245,6 +245,7 @@ class UiAutomationManager { } } + @SuppressWarnings("MissingPermissionAnnotation") private class UiAutomationService extends AbstractAccessibilityServiceConnection { private final Handler mMainHandler; diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index 903a07140eae..e54f0c12c0ca 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -82,7 +82,7 @@ import java.util.List; * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions * when a gesture starts or completes. */ -class GestureManifold implements GestureMatcher.StateChangeListener { +public class GestureManifold implements GestureMatcher.StateChangeListener { private static final String LOG_TAG = "GestureManifold"; @@ -111,7 +111,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener { // Shared state information. private TouchState mState; - GestureManifold(Context context, Listener listener, TouchState state, Handler handler) { + public GestureManifold(Context context, Listener listener, TouchState state, Handler handler) { mContext = context; mHandler = handler; mListener = listener; @@ -222,7 +222,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener { * @return True if the event has been appropriately handled by the gesture manifold and related * callback functions, false if it should be handled further by the calling function. */ - boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (mState.isClear()) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { // Validity safeguard: if touch state is clear, then matchers should always be clear diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 0a2a780adf45..baae1d934e8c 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -861,6 +861,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } final class DetectingStateWithMultiFinger extends DetectingState { + private static final int TWO_FINGER_GESTURE_MAX_TAPS = 2; // A flag set to true when two fingers have touched down. // Used to indicate what next finger action should be. private boolean mIsTwoFingerCountReached = false; @@ -917,7 +918,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); if (event.getPointerCount() == 2) { - if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) { + if (isMultiFingerMultiTapTriggered( + TWO_FINGER_GESTURE_MAX_TAPS - 1, event)) { // 3tap and hold afterLongTapTimeoutTransitionToDraggingState(event); } else { @@ -962,7 +964,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // (which is a rare combo to be used aside from magnification) if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) { transitionToViewportDraggingStateAndClear(event); - } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event) + } else if (isMultiFingerMultiTapTriggered( + TWO_FINGER_GESTURE_MAX_TAPS - 1, event) && event.getPointerCount() == 2) { transitionToViewportDraggingStateAndClear(event); } else if (isActivated() && event.getPointerCount() == 2) { @@ -1009,7 +1012,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mDisplayId, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); - } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) { + } else if (isMultiFingerMultiTapTriggered(TWO_FINGER_GESTURE_MAX_TAPS, event)) { // Placing multiple fingers before a single finger, because achieving a // multi finger multi tap also means achieving a single finger triple tap onTripleTap(event); @@ -1051,7 +1054,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mIsTwoFingerCountReached = false; } - if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) { + if (mDetectTwoFingerTripleTap && mCompletedTapCount > TWO_FINGER_GESTURE_MAX_TAPS - 1) { final boolean enabled = !isActivated(); mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled); } @@ -1075,7 +1078,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Only log the 3tap and hold event if (!shortcutTriggered) { final boolean enabled = !isActivated(); - if (mCompletedTapCount == 2) { + if (mCompletedTapCount == TWO_FINGER_GESTURE_MAX_TAPS - 1) { // Two finger triple tap and hold mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled); } else { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java index eff6488bc032..e11c36a91830 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java @@ -17,7 +17,7 @@ package com.android.server.accessibility.magnification; import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION; -import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION_CALLBACK; import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID; @@ -43,7 +43,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.MotionEvent; import android.view.accessibility.IMagnificationConnection; -import android.view.accessibility.IWindowMagnificationConnectionCallback; +import android.view.accessibility.IMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; import com.android.internal.accessibility.common.MagnificationConstants; @@ -922,16 +922,17 @@ public class MagnificationConnectionManager implements disableWindowMagnification(displayId, true); } - private class ConnectionCallback extends IWindowMagnificationConnectionCallback.Stub implements + @SuppressWarnings("MissingPermissionAnnotation") + private class ConnectionCallback extends IMagnificationConnectionCallback.Stub implements IBinder.DeathRecipient { private boolean mExpiredDeathRecipient = false; @Override public void onWindowMagnifierBoundsChanged(int displayId, Rect bounds) { if (mTrace.isA11yTracingEnabledForTypes( - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { mTrace.logTrace(TAG + "ConnectionCallback.onWindowMagnifierBoundsChanged", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, "displayId=" + displayId + ";bounds=" + bounds); } synchronized (mLock) { @@ -951,9 +952,9 @@ public class MagnificationConnectionManager implements public void onChangeMagnificationMode(int displayId, int magnificationMode) throws RemoteException { if (mTrace.isA11yTracingEnabledForTypes( - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { mTrace.logTrace(TAG + "ConnectionCallback.onChangeMagnificationMode", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, "displayId=" + displayId + ";mode=" + magnificationMode); } mCallback.onChangeMagnificationMode(displayId, magnificationMode); @@ -962,9 +963,9 @@ public class MagnificationConnectionManager implements @Override public void onSourceBoundsChanged(int displayId, Rect sourceBounds) { if (mTrace.isA11yTracingEnabledForTypes( - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { mTrace.logTrace(TAG + "ConnectionCallback.onSourceBoundsChanged", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, "displayId=" + displayId + ";source=" + sourceBounds); } synchronized (mLock) { @@ -980,9 +981,9 @@ public class MagnificationConnectionManager implements @Override public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) { if (mTrace.isA11yTracingEnabledForTypes( - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, "displayId=" + displayId + ";scale=" + scale + ";updatePersistence=" + updatePersistence); } @@ -992,9 +993,9 @@ public class MagnificationConnectionManager implements @Override public void onAccessibilityActionPerformed(int displayId) { if (mTrace.isA11yTracingEnabledForTypes( - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { mTrace.logTrace(TAG + "ConnectionCallback.onAccessibilityActionPerformed", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, "displayId=" + displayId); } mCallback.onAccessibilityActionPerformed(displayId); @@ -1003,9 +1004,9 @@ public class MagnificationConnectionManager implements @Override public void onMove(int displayId) { if (mTrace.isA11yTracingEnabledForTypes( - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { mTrace.logTrace(TAG + "ConnectionCallback.onMove", - FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, "displayId=" + displayId); } setTrackingTypingFocusEnabled(displayId, false); diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java index d7098a78d248..db5b3133169a 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java @@ -17,8 +17,8 @@ package com.android.server.accessibility.magnification; import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION; +import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION_CALLBACK; import static android.accessibilityservice.AccessibilityTrace.FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK; -import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK; import static android.os.IBinder.DeathRecipient; import android.annotation.NonNull; @@ -26,8 +26,8 @@ import android.annotation.Nullable; import android.os.RemoteException; import android.util.Slog; import android.view.accessibility.IMagnificationConnection; +import android.view.accessibility.IMagnificationConnectionCallback; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; import com.android.server.accessibility.AccessibilityTraceManager; @@ -217,13 +217,13 @@ class MagnificationConnectionWrapper { return true; } - boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) { + boolean setConnectionCallback(IMagnificationConnectionCallback connectionCallback) { if (mTrace.isA11yTracingEnabledForTypes( FLAGS_MAGNIFICATION_CONNECTION - | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) { + | FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) { mTrace.logTrace(TAG + ".setConnectionCallback", FLAGS_MAGNIFICATION_CONNECTION - | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, + | FLAGS_MAGNIFICATION_CONNECTION_CALLBACK, "callback=" + connectionCallback); } try { @@ -246,6 +246,7 @@ class MagnificationConnectionWrapper { return new RemoteAnimationCallback(callback, trace); } + @SuppressWarnings("MissingPermissionAnnotation") private static class RemoteAnimationCallback extends IRemoteMagnificationAnimationCallback.Stub { private final MagnificationAnimationCallback mCallback; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 73c267a6e262..75d01f574cac 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -481,10 +481,10 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl if (mDetectTwoFingerTripleTap) { mGestureMatchers.add(new MultiFingerMultiTap(context, /* fingers= */ 2, - /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP, + /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP, null)); mGestureMatchers.add(new MultiFingerMultiTapAndHold(context, /* fingers= */ 2, - /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD, + /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD, null)); } diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index ab678d93c665..b5130a1c4cfc 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -27,3 +27,10 @@ flag { description: "Mitigation for view state reset to empty causing no save dialog to show issue" bug: "297976948" } + +flag { + name: "include_invisible_view_group_in_assist_structure" + namespace: "autofill" + description: "Mitigation for autofill providers miscalculating view visibility" + bug: "291795358" +} diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index c111ec3213d9..1f89e57b90d7 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -253,13 +253,8 @@ class InputController { mInputManagerInternal.setDisplayEligibilityForPointerCapture(displayId, isEligible); } - void setLocalIme(int displayId) { - // WM throws a SecurityException if the display is untrusted. - if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) - == Display.FLAG_TRUSTED) { - mWindowManager.setDisplayImePolicy(displayId, - WindowManager.DISPLAY_IME_POLICY_LOCAL); - } + void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { + mWindowManager.setDisplayImePolicy(displayId, policy); } @GuardedBy("mLock") diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 13c79248eb38..58aa2c303345 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -899,6 +899,24 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { + super.setDisplayImePolicy_enforcePermission(); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplays.contains(displayId)) { + throw new SecurityException("Display ID " + displayId + + " not found for this virtual device"); + } + } + final long ident = Binder.clearCallingIdentity(); + try { + mInputController.setDisplayImePolicy(displayId, policy); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @Nullable public List<VirtualSensor> getVirtualSensorList() { super.getVirtualSensorList_enforcePermission(); @@ -1095,7 +1113,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mInputController.setPointerAcceleration(1f, displayId); mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false, displayId); - mInputController.setLocalIme(displayId); + // WM throws a SecurityException if the display is untrusted. + if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) + == Display.FLAG_TRUSTED) { + mInputController.setDisplayImePolicy(displayId, + WindowManager.DISPLAY_IME_POLICY_LOCAL); + } } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 923728ffb0f3..8dc6537d7e80 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -455,7 +455,7 @@ public class VirtualDeviceManagerService extends SystemService { cameraAccessController, mPendingTrampolineCallback, activityListener, soundEffectListener, runningAppsChangedCallback, params); if (Flags.expressMetrics()) { - Counter.logIncrement("virtual_devices.virtual_devices_created_count"); + Counter.logIncrement("virtual_devices.value_virtual_devices_created_count"); } synchronized (mVirtualDeviceManagerLock) { 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/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 31c9348c8127..136692eb90d5 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -222,6 +222,14 @@ public abstract class PackageManagerInternal { int callingUid); /** + * Like {@link #getInstalledApplications}, but allows the fetching of apps + * cross user. + */ + public abstract List<ApplicationInfo> getInstalledApplicationsCrossUser( + @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId, + int callingUid); + + /** * Retrieve launcher extras for a suspended package provided to the system in * {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, * PersistableBundle, String)}. @@ -1418,14 +1426,19 @@ public abstract class PackageManagerInternal { /** * Checks if package is quarantined for a specific user. + * + * @throws PackageManager.NameNotFoundException if the package is not found */ - public abstract boolean isPackageQuarantined(@NonNull String packageName, - @UserIdInt int userId); + public abstract boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException; /** * Checks if package is stopped for a specific user. + * + * @throws PackageManager.NameNotFoundException if the package is not found */ - public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId); + public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException; /** * Sends the PACKAGE_RESTARTED broadcast. diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index b9a3c0c41841..5a44ac803cb4 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -17,6 +17,7 @@ package com.android.server; import static android.os.Flags.stateOfHealthPublic; +import static android.os.Flags.batteryServiceSupportCurrentAdbCommand; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; import static com.android.server.health.Utils.copyV1Battery; @@ -927,9 +928,12 @@ public final class BatteryService extends SystemService { pw.println("Battery service (battery) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" get [-f] [ac|usb|wireless|dock|status|level|temp|present|counter|invalid]"); - pw.println(" set [-f] " - + "[ac|usb|wireless|dock|status|level|temp|present|counter|invalid] <value>"); + String getSetOptions = "ac|usb|wireless|dock|status|level|temp|present|counter|invalid"; + if (batteryServiceSupportCurrentAdbCommand()) { + getSetOptions += "|current_now|current_average"; + } + pw.println(" get [-f] [" + getSetOptions + "]"); + pw.println(" set [-f] [" + getSetOptions + "] <value>"); pw.println(" Force a battery property value, freezing battery state."); pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); pw.println(" unplug [-f]"); @@ -970,6 +974,7 @@ public final class BatteryService extends SystemService { unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw); } break; case "get": { + final int opts = parseOptions(shell); final String key = shell.getNextArg(); if (key == null) { pw.println("No property specified"); @@ -1001,6 +1006,22 @@ public final class BatteryService extends SystemService { case "counter": pw.println(mHealthInfo.batteryChargeCounterUah); break; + case "current_now": + if (batteryServiceSupportCurrentAdbCommand()) { + if ((opts & OPTION_FORCE_UPDATE) != 0) { + updateHealthInfo(); + } + pw.println(mHealthInfo.batteryCurrentMicroamps); + } + break; + case "current_average": + if (batteryServiceSupportCurrentAdbCommand()) { + if ((opts & OPTION_FORCE_UPDATE) != 0) { + updateHealthInfo(); + } + pw.println(mHealthInfo.batteryCurrentAverageMicroamps); + } + break; case "temp": pw.println(mHealthInfo.batteryTemperatureTenthsCelsius); break; @@ -1058,6 +1079,16 @@ public final class BatteryService extends SystemService { case "counter": mHealthInfo.batteryChargeCounterUah = Integer.parseInt(value); break; + case "current_now": + if (batteryServiceSupportCurrentAdbCommand()) { + mHealthInfo.batteryCurrentMicroamps = Integer.parseInt(value); + } + break; + case "current_average": + if (batteryServiceSupportCurrentAdbCommand()) { + mHealthInfo.batteryCurrentAverageMicroamps = + Integer.parseInt(value); + } case "temp": mHealthInfo.batteryTemperatureTenthsCelsius = Integer.parseInt(value); break; @@ -1101,6 +1132,14 @@ public final class BatteryService extends SystemService { return 0; } + private void updateHealthInfo() { + try { + mHealthServiceWrapper.scheduleUpdate(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to update health service data.", e); + } + } + private void setChargerAcOnline(boolean online, boolean forceUpdate) { if (!mUpdatesStopped) { copyV1Battery(mLastHealthInfo, mHealthInfo); diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 708da19232e1..5e9d1cbb32ca 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -110,6 +110,9 @@ }, { "name": "FrameworksNetTests" + }, + { + "name": "CtsSuspendAppsTestCases" } ], "presubmit-large": [ @@ -150,9 +153,6 @@ "name": "CtsPackageManagerTestCases" }, { - "name": "CtsSuspendAppsTestCases" - }, - { "name": "FrameworksServicesTests", "options": [ { 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/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 71916843fe0b..df8f17ac9d7c 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -5175,6 +5175,8 @@ public final class ActiveServices { return null; } + final long startTimeNs = SystemClock.elapsedRealtimeNanos(); + if (DEBUG_SERVICE) { Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent + " fg=" + r.fgRequired); } @@ -5333,9 +5335,14 @@ public final class ActiveServices { bringDownServiceLocked(r, enqueueOomAdj); return msg; } + mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r, + hostingRecord, true); if (isolated) { r.isolationHostProc = app; } + } else { + mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r, + hostingRecord, false); } if (r.fgRequired) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3e533a6ce601..2ee39c577977 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -421,6 +421,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; @@ -468,7 +469,6 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.SELinuxUtil; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.sdksandbox.SdkSandboxManagerLocal; @@ -1103,9 +1103,51 @@ public class ActivityManagerService extends IActivityManager.Stub private final ActivityMetricsLaunchObserver mActivityLaunchObserver = new ActivityMetricsLaunchObserver() { + @Override - public void onActivityLaunched(long id, ComponentName name, int temperature) { + public void onIntentStarted(@NonNull Intent intent, long timestampNanos) { + synchronized (this) { + mProcessList.getAppStartInfoTracker().onIntentStarted(intent, timestampNanos); + } + } + + @Override + public void onIntentFailed(long id) { + mProcessList.getAppStartInfoTracker().onIntentFailed(id); + } + + @Override + public void onActivityLaunched(long id, ComponentName name, int temperature, int userId) { mAppProfiler.onActivityLaunched(); + synchronized (ActivityManagerService.this) { + ProcessRecord record = null; + try { + record = getProcessRecordLocked(name.getPackageName(), mContext + .getPackageManager().getPackageUidAsUser(name.getPackageName(), 0, + userId)); + } catch (NameNotFoundException nnfe) { + // Ignore, record will be lost. + } + mProcessList.getAppStartInfoTracker().onActivityLaunched(id, name, temperature, + record); + } + } + + @Override + public void onActivityLaunchCancelled(long id) { + mProcessList.getAppStartInfoTracker().onActivityLaunchCancelled(id); + } + + @Override + public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos, + int launchMode) { + mProcessList.getAppStartInfoTracker().onActivityLaunchFinished(id, name, + timestampNanos, launchMode); + } + + @Override + public void onReportFullyDrawn(long id, long timestampNanos) { + mProcessList.getAppStartInfoTracker().onReportFullyDrawn(id, timestampNanos); } }; @@ -4488,13 +4530,13 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") private void attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) { - // Find the application record that is being attached... either via // the pid if we are running in multiple processes, or just pull the // next app record if we are emulating process with anonymous threads. ProcessRecord app; long startTime = SystemClock.uptimeMillis(); long bindApplicationTimeMillis; + long bindApplicationTimeNanos; if (pid != MY_PID && pid >= 0) { synchronized (mPidsSelfLocked) { app = mPidsSelfLocked.get(pid); @@ -4698,6 +4740,7 @@ public class ActivityManagerService extends IActivityManager.Stub checkTime(startTime, "attachApplicationLocked: immediately before bindApplication"); bindApplicationTimeMillis = SystemClock.uptimeMillis(); + bindApplicationTimeNanos = SystemClock.elapsedRealtimeNanos(); mAtmInternal.preBindApplication(app.getWindowProcessController()); final ActiveInstrumentation instr2 = app.getActiveInstrumentation(); if (mPlatformCompat != null) { @@ -4754,6 +4797,8 @@ public class ActivityManagerService extends IActivityManager.Stub } app.setBindApplicationTime(bindApplicationTimeMillis); + mProcessList.getAppStartInfoTracker() + .reportBindApplicationTimeNanos(app, bindApplicationTimeNanos); // Make app active after binding application or client may be running requests (e.g // starting activities) before it is ready. @@ -9799,12 +9844,12 @@ public class ActivityManagerService extends IActivityManager.Stub final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid, "getHistoricalProcessStartReasons"); if (uid != INVALID_UID) { - mProcessList.mAppStartInfoTracker.getStartInfo( + mProcessList.getAppStartInfoTracker().getStartInfo( packageName, userId, callingPid, maxNum, results); } } else { // If no package name is given, use the caller's uid as the filter uid. - mProcessList.mAppStartInfoTracker.getStartInfo( + mProcessList.getAppStartInfoTracker().getStartInfo( packageName, callingUid, callingPid, maxNum, results); } return new ParceledListSlice<ApplicationStartInfo>(results); @@ -9822,7 +9867,7 @@ public class ActivityManagerService extends IActivityManager.Stub } final int callingUid = Binder.getCallingUid(); - mProcessList.mAppStartInfoTracker.addStartInfoCompleteListener(listener, callingUid); + mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener, callingUid); } @@ -9836,7 +9881,7 @@ public class ActivityManagerService extends IActivityManager.Stub } final int callingUid = Binder.getCallingUid(); - mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true); + mProcessList.getAppStartInfoTracker().clearStartInfoCompleteListener(callingUid, true); } @Override @@ -10138,7 +10183,7 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); - mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage); + mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage); pw.println("-------------------------------------------------------------------------------"); mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage); } @@ -10541,7 +10586,7 @@ public class ActivityManagerService extends IActivityManager.Stub dumpPackage = args[opti]; opti++; } - mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage); + mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage); } else if ("exit-info".equals(cmd)) { if (opti < args.length) { dumpPackage = args[opti]; @@ -11972,6 +12017,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean dumpSwapPss; boolean dumpProto; boolean mDumpPrivateDirty; + boolean mDumpAllocatorStats; } @NeverCompile // Avoid size overhead of debugging code. @@ -11991,6 +12037,7 @@ public class ActivityManagerService extends IActivityManager.Stub opts.dumpSwapPss = false; opts.dumpProto = asProto; opts.mDumpPrivateDirty = false; + opts.mDumpAllocatorStats = false; int opti = 0; while (opti < args.length) { @@ -12027,7 +12074,8 @@ public class ActivityManagerService extends IActivityManager.Stub opts.isCheckinRequest = true; } else if ("--proto".equals(opt)) { opts.dumpProto = true; - + } else if ("--logstats".equals(opt)) { + opts.mDumpAllocatorStats = true; } else if ("-h".equals(opt)) { pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]"); pw.println(" -a: include all available information for each process."); @@ -12238,7 +12286,8 @@ public class ActivityManagerService extends IActivityManager.Stub try { thread.dumpMemInfo(tp.getWriteFd(), mi, opts.isCheckinRequest, opts.dumpFullDetails, - opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs); + opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, + opts.mDumpAllocatorStats, innerArgs); tp.go(fd, opts.dumpUnreachable ? 30000 : 5000); } finally { tp.kill(); @@ -13827,6 +13876,7 @@ public class ActivityManagerService extends IActivityManager.Stub // activity manager to announce its creation. public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId, @BackupDestination int backupDestination) { + long startTimeNs = SystemClock.elapsedRealtimeNanos(); if (DEBUG_BACKUP) { Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode + " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid() @@ -13902,15 +13952,20 @@ public class ActivityManagerService extends IActivityManager.Stub ? new ComponentName(app.packageName, app.backupAgentName) : new ComponentName("android", "FullBackupAgent"); - // startProcessLocked() returns existing proc's record if it's already running - ProcessRecord proc = startProcessLocked(app.processName, app, - false, 0, - new HostingRecord(HostingRecord.HOSTING_TYPE_BACKUP, hostingName), - ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false); + ProcessRecord proc = getProcessRecordLocked(app.processName, app.uid); + boolean isProcessStarted = proc != null; + if (!isProcessStarted) { + proc = startProcessLocked(app.processName, app, + false, 0, + new HostingRecord(HostingRecord.HOSTING_TYPE_BACKUP, hostingName), + ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false); + } if (proc == null) { Slog.e(TAG, "Unable to start backup agent process " + r); return false; } + mProcessList.getAppStartInfoTracker().handleProcessBackupStart(startTimeNs, proc, r, + !isProcessStarted); // If the app is a regular app (uid >= 10000) and not the system server or phone // process, etc, then mark it as being in full backup so that certain calls to the @@ -17510,6 +17565,8 @@ public class ActivityManagerService extends IActivityManager.Stub * other {@code ActivityManager#USER_OP_*} codes for failure. * */ + // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows + // delayed locking behavior once the private space flag is finalized. @Override public int stopUserWithDelayedLocking(final int userId, boolean force, final IStopUserCallback callback) { @@ -18735,8 +18792,12 @@ public class ActivityManagerService extends IActivityManager.Stub // If the process is known as top app, set a hint so when the process is // started, the top priority can be applied immediately to avoid cpu being // preempted by other processes before attaching the process of top app. - startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */, - new HostingRecord(hostingType, hostingName, isTop), + final long startTimeNs = SystemClock.elapsedRealtimeNanos(); + HostingRecord hostingRecord = + new HostingRecord(hostingType, hostingName, isTop); + ProcessRecord rec = getProcessRecordLocked(processName, info.uid); + ProcessRecord app = startProcessLocked(processName, info, knownToBeDead, + 0 /* intentFlags */, hostingRecord, ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */, false /* isolated */); } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index f3b2ef34ad6d..ae0cd65b2770 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -1362,7 +1362,7 @@ final class ActivityManagerShellCommand extends ShellCommand { } userId = user.id; } - mInternal.mProcessList.mAppStartInfoTracker + mInternal.mProcessList.getAppStartInfoTracker() .clearHistoryProcessStartInfo(packageName, userId); return 0; } diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index edca74fae0e4..82e554e67b7e 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -22,11 +22,12 @@ import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.app.ActivityOptions; +import android.annotation.NonNull; import android.app.ApplicationStartInfo; import android.app.Flags; import android.app.IApplicationStartInfoCompleteListener; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -138,6 +139,15 @@ public final class AppStartInfoTracker { /** The path to the historical proc start info file, persisted in the storage. */ @VisibleForTesting File mProcStartInfoFile; + + /** + * Temporary list of records that have not been completed. + * + * Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}. + */ + @GuardedBy("mLock") + private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>(); + AppStartInfoTracker() { mCallbacks = new SparseArray<>(); mData = new ProcessMap<AppStartInfoContainer>(); @@ -174,68 +184,99 @@ public final class AppStartInfoTracker { }); } - void handleProcessColdStarted(long startTimeNs, HostingRecord hostingRecord, - ProcessRecord app) { + void onIntentStarted(@NonNull Intent intent, long timestampNanos) { synchronized (mLock) { if (!mEnabled) { return; } ApplicationStartInfo start = new ApplicationStartInfo(); - addBaseFieldsFromProcessRecord(start, app); start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); - start.addStartupTimestamp( - ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); - start.addStartupTimestamp( - ApplicationStartInfo.START_TIMESTAMP_FORK, app.getStartElapsedTime()); - start.setStartType(ApplicationStartInfo.START_TYPE_COLD); - start.setReason(ApplicationStartInfo.START_REASON_OTHER); - addStartInfoLocked(start); + start.setIntent(intent); + start.setStartType(ApplicationStartInfo.START_TYPE_UNSET); + start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos); + if (intent != null && intent.getCategories() != null + && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { + start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER); + } else { + start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY); + } + mInProgRecords.put(timestampNanos, start); } } - public void handleProcessActivityWarmOrHotStarted(long startTimeNs, - ActivityOptions activityOptions, Intent intent) { + void onIntentFailed(long id) { synchronized (mLock) { if (!mEnabled) { return; } - ApplicationStartInfo start = new ApplicationStartInfo(); - start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); - start.addStartupTimestamp( - ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); - start.setIntent(intent); - start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER); - if (activityOptions != null) { - start.setProcessName(activityOptions.getPackageName()); + if (!mInProgRecords.containsKey(id)) { + return; } - start.setStartType(ApplicationStartInfo.START_TYPE_WARM); - if (intent != null && intent.getCategories() != null - && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) { - start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER); + mInProgRecords.get(id).setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR); + mInProgRecords.remove(id); + } + } + + void onActivityLaunched(long id, ComponentName name, long temperature, ProcessRecord app) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (!mInProgRecords.containsKey(id)) { + return; + } + if (app != null) { + ApplicationStartInfo info = mInProgRecords.get(id); + info.setStartType((int) temperature); + addBaseFieldsFromProcessRecord(info, app); + addStartInfoLocked(info); } else { - start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY); + mInProgRecords.remove(id); } - addStartInfoLocked(start); } } - public void handleProcessActivityStartedFromRecents(long startTimeNs, - ActivityOptions activityOptions) { + void onActivityLaunchCancelled(long id) { synchronized (mLock) { if (!mEnabled) { return; } - ApplicationStartInfo start = new ApplicationStartInfo(); - start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED); - start.addStartupTimestamp( - ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs); - if (activityOptions != null) { - start.setIntent(activityOptions.getResultData()); - start.setProcessName(activityOptions.getPackageName()); + if (!mInProgRecords.containsKey(id)) { + return; } - start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS); - start.setStartType(ApplicationStartInfo.START_TYPE_WARM); - addStartInfoLocked(start); + ApplicationStartInfo info = mInProgRecords.get(id); + info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR); + mInProgRecords.remove(id); + } + } + + void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos, + int launchMode) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (!mInProgRecords.containsKey(id)) { + return; + } + ApplicationStartInfo info = mInProgRecords.get(id); + info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); + info.setLaunchMode(launchMode); + } + } + + void onReportFullyDrawn(long id, long timestampNanos) { + synchronized (mLock) { + if (!mEnabled) { + return; + } + if (!mInProgRecords.containsKey(id)) { + return; + } + ApplicationStartInfo info = mInProgRecords.get(id); + info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN, + timestampNanos); + mInProgRecords.remove(id); } } @@ -347,7 +388,8 @@ public final class AppStartInfoTracker { ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE); } - void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) { + /** Report a bind application timestamp to add to {@link ApplicationStartInfo}. */ + public void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) { addTimestampToStart(app, timeNs, ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION); } 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/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 903cb7bcfaed..982076dd7c9a 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -30,7 +30,6 @@ import android.widget.WidgetFlags; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import java.util.ArrayList; import java.util.HashMap; @@ -164,12 +163,6 @@ final class CoreSettingsObserver extends ContentObserver { WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT)); sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION, - SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class, - SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT)); - - sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>( TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU, TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class, TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT)); diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java index caafb421a147..fc8ad6bc94e7 100644 --- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java +++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java @@ -35,6 +35,7 @@ import android.app.ActivityManager.ForegroundServiceApiType; import android.app.ForegroundServiceDelegationOptions; import android.content.ComponentName; import android.content.pm.ServiceInfo; +import android.os.Trace; import android.util.ArrayMap; import android.util.IntArray; import android.util.LongArray; @@ -134,6 +135,11 @@ public class ForegroundServiceTypeLoggerModule { * call of the right type will also be associated and logged */ public void logForegroundServiceStart(int uid, int pid, ServiceRecord record) { + if (record.getComponentName() != null) { + final String traceTag = record.getComponentName().flattenToString() + ":" + uid; + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, traceTag, + "foregroundService", record.foregroundServiceType); + } // initialize the UID stack UidState uidState = mUids.get(uid); if (uidState == null) { @@ -205,6 +211,11 @@ public class ForegroundServiceTypeLoggerModule { // we need to log all the API end events and remove the start events // then we remove the FGS from the various stacks // and also clean up the start calls stack by UID + if (record.getComponentName() != null) { + final String traceTag = record.getComponentName().flattenToString() + ":" + uid; + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, + traceTag, record.hashCode()); + } final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType); final UidState uidState = mUids.get(uid); if (apiTypes.size() == 0) { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4ff34b1d7faa..3156e9da0ae9 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -498,7 +498,7 @@ public final class ProcessList { /** Manages the {@link android.app.ApplicationStartInfo} records. */ @GuardedBy("mAppStartInfoTracker") - final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker(); + private final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker(); /** * The currently running SDK sandbox processes for a uid. @@ -1523,6 +1523,10 @@ public final class ProcessList { return mCachedRestoreLevel; } + AppStartInfoTracker getAppStartInfoTracker() { + return mAppStartInfoTracker; + } + /** * Set the out-of-memory badness adjustment for a process. * If {@code pid <= 0}, this method will be a no-op. @@ -2572,6 +2576,7 @@ public final class ProcessList { boolean isSdkSandbox, int sdkSandboxUid, String sdkSandboxClientAppPackage, String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) { long startTime = SystemClock.uptimeMillis(); + final long startTimeNs = SystemClock.elapsedRealtimeNanos(); ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(processName, info.uid); 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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 47a99fe24ec4..a6b532cdef09 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -356,6 +356,8 @@ class UserController implements Handler.Callback { * Once total number of unlocked users reach mMaxRunningUsers, least recently used user * will be locked. */ + // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows + // delayed locking behavior once the private space flag is finalized. @GuardedBy("mLock") private boolean mDelayUserDataLocking; @@ -365,11 +367,12 @@ class UserController implements Handler.Callback { private volatile boolean mAllowUserUnlocking; /** - * Keep track of last active users for mDelayUserDataLocking. - * The latest stopped user is placed in front while the least recently stopped user in back. + * Keep track of last active users for delayUserDataLocking. + * The most recently stopped user with delayed locking is placed in front, while the least + * recently stopped user in back. */ @GuardedBy("mLock") - private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>(); + private final ArrayList<Integer> mLastActiveUsersForDelayedLocking = new ArrayList<>(); /** * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet- @@ -1011,20 +1014,21 @@ class UserController implements Handler.Callback { Slogf.i(TAG, "stopSingleUserLU userId=" + userId); final UserState uss = mStartedUsers.get(userId); if (uss == null) { // User is not started - // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock - // the requested user as the client wants to stop and lock the user. On the other hand, - // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking - // is set as that means client wants to lock the user immediately. - // If mDelayUserDataLocking is not set, the user was already locked when it was stopped - // and no further action is necessary. - if (mDelayUserDataLocking) { + // If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need + // to lock the requested user as the client wants to stop and lock the user. On the + // other hand, having keyEvictedCallback set will lead into locking user if + // canDelayDataLockingForUser() is true as that means client wants to lock the user + // immediately. + // If canDelayDataLockingForUser() is false, the user was already locked when it was + // stopped and no further action is necessary. + if (canDelayDataLockingForUser(userId)) { if (allowDelayedLocking && keyEvictedCallback != null) { Slogf.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it" + " and lock user:" + userId, new RuntimeException()); allowDelayedLocking = false; } if (!allowDelayedLocking) { - if (mLastActiveUsers.remove(Integer.valueOf(userId))) { + if (mLastActiveUsersForDelayedLocking.remove(Integer.valueOf(userId))) { // should lock the user, user is already gone final ArrayList<KeyEvictedCallback> keyEvictedCallbacks; if (keyEvictedCallback != null) { @@ -1354,14 +1358,21 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) { int userIdToLock = userId; - if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral() + // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to + // state maximum running unlocked users specifically + if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking + && !getUserInfo(userId).isEphemeral() && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) { - mLastActiveUsers.remove((Integer) userId); // arg should be object, not index - mLastActiveUsers.add(0, userId); - int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsers.size(); + // arg should be object, not index + mLastActiveUsersForDelayedLocking.remove((Integer) userId); + mLastActiveUsersForDelayedLocking.add(0, userId); + int totalUnlockedUsers = mStartedUsers.size() + + mLastActiveUsersForDelayedLocking.size(); if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user - userIdToLock = mLastActiveUsers.get(mLastActiveUsers.size() - 1); - mLastActiveUsers.remove(mLastActiveUsers.size() - 1); + userIdToLock = mLastActiveUsersForDelayedLocking.get( + mLastActiveUsersForDelayedLocking.size() - 1); + mLastActiveUsersForDelayedLocking + .remove(mLastActiveUsersForDelayedLocking.size() - 1); Slogf.i(TAG, "finishUserStopped, stopping user:" + userId + " lock user:" + userIdToLock); } else { @@ -1374,6 +1385,24 @@ class UserController implements Handler.Callback { } /** + * Returns whether the user can have its CE storage left unlocked, even when it is stopped, + * either due to a global device configuration or an individual user's property. + */ + private boolean canDelayDataLockingForUser(@UserIdInt int userIdToLock) { + if (allowBiometricUnlockForPrivateProfile()) { + final UserProperties userProperties = getUserProperties(userIdToLock); + return (mDelayUserDataLocking || (userProperties != null + && userProperties.getAllowStoppingUserWithDelayedLocking())); + } + return mDelayUserDataLocking; + } + + private boolean allowBiometricUnlockForPrivateProfile() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace(); + } + + /** * Determines the list of users that should be stopped together with the specified * {@code userId}. The returned list includes {@code userId}. */ @@ -3161,7 +3190,7 @@ class UserController implements Handler.Callback { pw.println(" mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds)); pw.println(" mCurrentUserId:" + mCurrentUserId); pw.println(" mTargetUserId:" + mTargetUserId); - pw.println(" mLastActiveUsers:" + mLastActiveUsers); + pw.println(" mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); pw.println(" mAllowUserUnlocking:" + mAllowUserUnlocking); pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 824bdd4ccb79..32d5cf587e0c 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -19,6 +19,7 @@ package com.android.server.app; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.EXTRA_REPLACING; +import static android.server.app.Flags.gameDefaultFrameRate; import static com.android.internal.R.styleable.GameModeConfig_allowGameAngleDriver; import static com.android.internal.R.styleable.GameModeConfig_allowGameDownscaling; @@ -28,6 +29,7 @@ import static com.android.internal.R.styleable.GameModeConfig_supportsPerformanc import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import android.Manifest; +import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -66,11 +68,13 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PermissionEnforcer; import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.os.SystemProperties; import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; @@ -138,6 +142,10 @@ public final class GameManagerService extends IGameManagerService.Stub { static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6; static final int WRITE_DELAY_MILLIS = 10 * 1000; // 10 seconds static final int LOADING_BOOST_MAX_DURATION = 5 * 1000; // 5 seconds + static final String PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED = + "persist.graphics.game_default_frame_rate.enabled"; + static final String PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE = + "ro.surface_flinger.game_default_frame_rate_override"; private static final String PACKAGE_NAME_MSG_KEY = "packageName"; private static final String USER_ID_MSG_KEY = "userId"; @@ -154,7 +162,6 @@ public final class GameManagerService extends IGameManagerService.Stub { private final PackageManager mPackageManager; private final UserManager mUserManager; private final PowerManagerInternal mPowerManagerInternal; - private final File mSystemDir; @VisibleForTesting final AtomicFile mGameModeInterventionListFile; private DeviceConfigListener mDeviceConfigListener; @@ -175,28 +182,57 @@ public final class GameManagerService extends IGameManagerService.Stub { final MyUidObserver mUidObserver; @GuardedBy("mUidObserverLock") private final Set<Integer> mForegroundGameUids = new HashSet<>(); + private final GameManagerServiceSystemPropertiesWrapper mSysProps; + private float mGameDefaultFrameRateValue; + + @VisibleForTesting + static class Injector { + public GameManagerServiceSystemPropertiesWrapper createSystemPropertiesWrapper() { + return new GameManagerServiceSystemPropertiesWrapper() { + @Override + public String get(String key, String def) { + return SystemProperties.get(key, def); + } + @Override + public boolean getBoolean(String key, boolean def) { + return SystemProperties.getBoolean(key, def); + } + + @Override + public int getInt(String key, int def) { + return SystemProperties.getInt(key, def); + } + + @Override + public void set(String key, String val) { + SystemProperties.set(key, val); + } + }; + } + } public GameManagerService(Context context) { this(context, createServiceThread().getLooper()); } GameManagerService(Context context, Looper looper) { - this(context, looper, Environment.getDataDirectory()); + this(context, looper, Environment.getDataDirectory(), new Injector()); } @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - GameManagerService(Context context, Looper looper, File dataDir) { + GameManagerService(Context context, Looper looper, File dataDir, Injector injector) { + super(PermissionEnforcer.fromContext(context)); mContext = context; mHandler = new SettingsHandler(looper); mPackageManager = mContext.getPackageManager(); mUserManager = mContext.getSystemService(UserManager.class); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); - mSystemDir = new File(dataDir, "system"); - mSystemDir.mkdirs(); - FileUtils.setPermissions(mSystemDir.toString(), + File systemDir = new File(dataDir, "system"); + systemDir.mkdirs(); + FileUtils.setPermissions(systemDir.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH, -1, -1); - mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir, + mGameModeInterventionListFile = new AtomicFile(new File(systemDir, GAME_MODE_INTERVENTION_LIST_FILE_NAME)); FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(), FileUtils.S_IRUSR | FileUtils.S_IWUSR @@ -220,6 +256,8 @@ public final class GameManagerService extends IGameManagerService.Stub { } catch (RemoteException e) { Slog.w(TAG, "Could not register UidObserver"); } + + mSysProps = injector.createSystemPropertiesWrapper(); } @Override @@ -1521,6 +1559,10 @@ public final class GameManagerService extends IGameManagerService.Stub { mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false); Slog.v(TAG, "Game power mode OFF (game manager service start/restart)"); mPowerManagerInternal.setPowerMode(Mode.GAME, false); + + mGameDefaultFrameRateValue = (float) mSysProps.getInt( + PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE, 60); + Slog.v(TAG, "Game Default Frame Rate : " + mGameDefaultFrameRateValue); } private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) { @@ -1588,7 +1630,7 @@ public final class GameManagerService extends IGameManagerService.Stub { try { final float fps = 0.0f; final int uid = mPackageManager.getPackageUidAsUser(packageName, userId); - setOverrideFrameRate(uid, fps); + setGameModeFrameRateOverride(uid, fps); } catch (PackageManager.NameNotFoundException e) { return; } @@ -1620,7 +1662,7 @@ public final class GameManagerService extends IGameManagerService.Stub { try { final float fps = modeConfig.getFps(); final int uid = mPackageManager.getPackageUidAsUser(packageName, userId); - setOverrideFrameRate(uid, fps); + setGameModeFrameRateOverride(uid, fps); } catch (PackageManager.NameNotFoundException e) { return; } @@ -2159,14 +2201,69 @@ public final class GameManagerService extends IGameManagerService.Stub { } @VisibleForTesting - void setOverrideFrameRate(int uid, float frameRate) { - nativeSetOverrideFrameRate(uid, frameRate); + void setGameModeFrameRateOverride(int uid, float frameRate) { + nativeSetGameModeFrameRateOverride(uid, frameRate); + } + + @VisibleForTesting + void setGameDefaultFrameRateOverride(int uid, float frameRate) { + Slog.v(TAG, "setDefaultFrameRateOverride : " + uid + " , " + frameRate); + nativeSetGameDefaultFrameRateOverride(uid, frameRate); + } + + private float getGameDefaultFrameRate() { + final boolean isGameDefaultFrameRateEnabled; + float gameDefaultFrameRate = 0.0f; + synchronized (mLock) { + isGameDefaultFrameRateEnabled = + mSysProps.getBoolean( + PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED, true); + } + if (gameDefaultFrameRate()) { + gameDefaultFrameRate = isGameDefaultFrameRateEnabled + ? mGameDefaultFrameRateValue : 0.0f; + } + return gameDefaultFrameRate; + } + + @Override + @EnforcePermission(Manifest.permission.MANAGE_GAME_MODE) + public void toggleGameDefaultFrameRate(boolean isEnabled) { + toggleGameDefaultFrameRate_enforcePermission(); + if (gameDefaultFrameRate()) { + Slog.v(TAG, "toggleGameDefaultFrameRate : " + isEnabled); + this.toggleGameDefaultFrameRateUnchecked(isEnabled); + } + } + + private void toggleGameDefaultFrameRateUnchecked(boolean isEnabled) { + // Update system properties. + // Here we only need to immediately update games that are in the foreground. + // We will update game default frame rate when a game comes into foreground in + // MyUidObserver. + synchronized (mLock) { + if (isEnabled) { + mSysProps.set( + PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED, "true"); + } else { + mSysProps.set( + PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED, "false"); + } + } + + // Update all foreground games' frame rate. + synchronized (mUidObserverLock) { + for (int uid : mForegroundGameUids) { + setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate()); + } + } } /** * load dynamic library for frame rate overriding JNI calls */ - private static native void nativeSetOverrideFrameRate(int uid, float frameRate); + private static native void nativeSetGameModeFrameRateOverride(int uid, float frameRate); + private static native void nativeSetGameDefaultFrameRateOverride(int uid, float frameRate); final class MyUidObserver extends UidObserver { @Override @@ -2179,6 +2276,7 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { synchronized (mUidObserverLock) { + if (procState != ActivityManager.PROCESS_STATE_TOP) { disableGameMode(uid); return; @@ -2198,6 +2296,7 @@ public final class GameManagerService extends IGameManagerService.Stub { Slog.v(TAG, "Game power mode ON (process state was changed to foreground)"); mPowerManagerInternal.setPowerMode(Mode.GAME, true); } + setGameDefaultFrameRateOverride(uid, getGameDefaultFrameRate()); mForegroundGameUids.add(uid); } } diff --git a/services/core/java/com/android/server/app/GameManagerServiceSystemPropertiesWrapper.java b/services/core/java/com/android/server/app/GameManagerServiceSystemPropertiesWrapper.java new file mode 100644 index 000000000000..afacedaac11b --- /dev/null +++ b/services/core/java/com/android/server/app/GameManagerServiceSystemPropertiesWrapper.java @@ -0,0 +1,67 @@ +/* + * 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.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.SystemProperties; +/** + * Wrapper interface to access {@link SystemProperties}. + * + * @hide + */ +interface GameManagerServiceSystemPropertiesWrapper { + /** + * Get the String value for the given {@code key}. + * + * @param key the key to lookup + * @param def the default value in case the property is not set or empty + * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty + * string otherwise + */ + @NonNull + String get(@NonNull String key, @Nullable String def); + /** + * Get the Boolean value for the given {@code key}. + * + * @param key the key to lookup + * @param def the default value in case the property is not set or empty + * @return if the {@code key} isn't found, return {@code def} if it isn't null, not parsable + * or an empty string otherwise + */ + @NonNull + boolean getBoolean(@NonNull String key, boolean def); + + /** + * Get the Integer value for the given {@code key}. + * + * @param key the key to lookup + * @param def the default value in case the property is not set or empty + * @return if the {@code key} isn't found, return {@code def} if it isn't null, not parsable + * or an empty string otherwise + */ + @NonNull + int getInt(@NonNull String key, int def); + /** + * Set the value for the given {@code key} to {@code val}. + * + * @throws IllegalArgumentException if the {@code val} exceeds 91 characters + * @throws RuntimeException if the property cannot be set, for example, if it was blocked by + * SELinux. libc will log the underlying reason. + */ + void set(@NonNull String key, @Nullable String val); +} diff --git a/services/core/java/com/android/server/app/flags.aconfig b/services/core/java/com/android/server/app/flags.aconfig new file mode 100644 index 000000000000..f2e4783bd9eb --- /dev/null +++ b/services/core/java/com/android/server/app/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.server.app" + +flag { + name: "game_default_frame_rate" + namespace: "game" + description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature." + bug: "286084594" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index ffdab7dfbfa4..9ae43a03515a 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -235,9 +235,10 @@ public final class AdiDeviceState { * {@link AdiDeviceState#toPersistableString()}. */ public static int getPeristedMaxSize() { - return 36; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1 + return 39; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1 + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1 - + (SETTINGS_FIELD_SEPARATOR)5 */ + + (mAudioDeviceCategory)1 + (SETTINGS_FIELD_SEPARATOR)6 + + (SETTING_DEVICE_SEPARATOR)1 */ } @Nullable diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 9cfcb1679429..80917533cce1 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -2037,9 +2037,8 @@ public class AudioDeviceBroker { } break; case MSG_L_UPDATED_ADI_DEVICE_STATE: - synchronized (mDeviceStateLock) { - mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj); - } break; + mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj); + break; default: Log.wtf(TAG, "Invalid message " + msg.what); @@ -2687,11 +2686,15 @@ public class AudioDeviceBroker { return; } final SettingsAdapter settingsAdapter = mAudioService.getSettings(); - boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(), - Settings.Secure.AUDIO_DEVICE_INVENTORY, - deviceSettings, UserHandle.USER_CURRENT); - if (!res) { - Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings); + try { + boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(), + Settings.Secure.AUDIO_DEVICE_INVENTORY, + deviceSettings, UserHandle.USER_CURRENT); + if (!res) { + Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings); + } + } catch (IllegalArgumentException e) { + Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings, e); } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index d077ebc9c86c..34cfdfaa7974 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -30,7 +30,6 @@ import static android.media.AudioSystem.isBluetoothOutDevice; import static android.media.AudioSystem.isBluetoothScoOutDevice; import static android.media.audio.Flags.automaticBtDeviceType; - import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import android.annotation.NonNull; @@ -81,7 +80,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -104,6 +102,14 @@ public class AudioDeviceInventory { private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|"; private static final String SETTING_DEVICE_SEPARATOR = "\\|"; + /** Max String length that can be persisted within the Settings. */ + // LINT.IfChange(settings_max_length_per_string) + private static final int MAX_SETTINGS_LENGTH_PER_STRING = 32768; + // LINT.ThenChange(/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java) + + private static final int MAX_DEVICE_INVENTORY_ENTRIES = + MAX_SETTINGS_LENGTH_PER_STRING / AdiDeviceState.getPeristedMaxSize(); + // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices private final Object mDevicesLock = new Object(); @@ -113,7 +119,8 @@ public class AudioDeviceInventory { private final Object mDeviceInventoryLock = new Object(); @GuardedBy("mDeviceInventoryLock") - private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>(); + private final LinkedHashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = + new LinkedHashMap<>(); Collection<AdiDeviceState> getImmutableDeviceInventory() { final List<AdiDeviceState> newList; @@ -136,6 +143,7 @@ public class AudioDeviceInventory { oldState.setSAEnabled(newState.isSAEnabled()); return oldState; }); + checkDeviceInventorySize_l(); } mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); } @@ -173,6 +181,8 @@ public class AudioDeviceInventory { ads.setAudioDeviceCategory(category); mDeviceInventory.put(ads.getDeviceId(), ads); + checkDeviceInventorySize_l(); + mDeviceBroker.postUpdatedAdiDeviceState(ads); mDeviceBroker.postPersistAudioDeviceSettings(); } @@ -200,6 +210,7 @@ public class AudioDeviceInventory { } return oldState; }); + checkDeviceInventorySize_l(); } if (updatedCategory.get()) { mDeviceBroker.postUpdatedAdiDeviceState(deviceState); @@ -272,6 +283,18 @@ public class AudioDeviceInventory { } } + @GuardedBy("mDeviceInventoryLock") + private void checkDeviceInventorySize_l() { + if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) { + // remove the first element + Iterator<Entry<Pair<Integer, String>, AdiDeviceState>> iterator = + mDeviceInventory.entrySet().iterator(); + if (iterator.hasNext()) { + iterator.remove(); + } + } + } + @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"}) private boolean synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice) { for (DeviceInfo di : mConnectedDevices.values()) { @@ -2806,7 +2829,7 @@ public class AudioDeviceInventory { deviceCatalogSize = mDeviceInventory.size(); final StringBuilder settingsBuilder = new StringBuilder( - deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); + deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator(); if (iterator.hasNext()) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 44cb1367928d..290bb7e92c69 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5106,7 +5106,7 @@ public class AudioService extends IAudioService.Stub private void setMasterMuteInternalNoCallerCheck( boolean mute, int flags, int userId, String eventSource) { if (DEBUG_VOL) { - Log.d(TAG, TextUtils.formatSimple("Master mute %s, %d, user=%d from %s", + Log.d(TAG, TextUtils.formatSimple("Master mute %s, flags 0x%x, userId=%d from %s", mute, flags, userId, eventSource)); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 5084b602bff2..578d9dc2aede 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -722,6 +722,7 @@ public class FaceService extends SystemService { if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) { if (virtualAt != -1) { //only virtual instance should be returned + Slog.i(TAG, "virtual hal is used"); return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt))); } else { Slog.e(TAG, "Could not find virtual interface while it is enabled"); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 5ce0c8b384ef..769554315b6e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -1057,6 +1057,7 @@ public class FingerprintService extends SystemService { if (Utils.isVirtualEnabled(getContext())) { if (virtualAt != -1) { //only virtual instance should be returned + Slog.i(TAG, "virtual hal is used"); return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt))); } else { Slog.e(TAG, "Could not find virtual interface while it is enabled"); diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 3b7d80c844de..df179a9b6d65 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -1292,7 +1292,11 @@ public class SyncManager { */ private boolean isPackageStopped(String packageName, int userId) { if (android.content.pm.Flags.stayStopped()) { - return mPackageManagerInternal.isPackageStopped(packageName, userId); + try { + return mPackageManagerInternal.isPackageStopped(packageName, userId); + } catch (NameNotFoundException e) { + Log.e(TAG, "Couldn't determine stopped state for unknown package: " + packageName); + } } return false; } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index dff14b5fbdd0..6ec6a123a4e7 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -327,7 +327,7 @@ public final class DeviceStateManagerService extends SystemService { Optional<DeviceState> getOverrideState() { synchronized (mLock) { if (mActiveOverride.isPresent()) { - return getStateLocked(mActiveOverride.get().getRequestedState()); + return getStateLocked(mActiveOverride.get().getRequestedStateIdentifier()); } return Optional.empty(); } @@ -342,7 +342,7 @@ public final class DeviceStateManagerService extends SystemService { Optional<DeviceState> getOverrideBaseState() { synchronized (mLock) { if (mActiveBaseStateOverride.isPresent()) { - return getStateLocked(mActiveBaseStateOverride.get().getRequestedState()); + return getStateLocked(mActiveBaseStateOverride.get().getRequestedStateIdentifier()); } return Optional.empty(); } @@ -499,6 +499,7 @@ public final class DeviceStateManagerService extends SystemService { * @return {@code true} if the pending state has changed as a result of this call, {@code false} * otherwise. */ + @GuardedBy("mLock") private boolean updatePendingStateLocked() { if (mPendingState.isPresent()) { // Have pending state, can not configure a new state until the state is committed. @@ -507,7 +508,8 @@ public final class DeviceStateManagerService extends SystemService { final DeviceState stateToConfigure; if (mActiveOverride.isPresent()) { - stateToConfigure = getStateLocked(mActiveOverride.get().getRequestedState()).get(); + stateToConfigure = getStateLocked( + mActiveOverride.get().getRequestedStateIdentifier()).get(); } else if (mBaseState.isPresent() && isSupportedStateLocked(mBaseState.get().getIdentifier())) { // Base state could have recently become unsupported after a change in supported states. @@ -599,7 +601,7 @@ public final class DeviceStateManagerService extends SystemService { // requested state is committed. OverrideRequest activeRequest = mActiveOverride.orElse(null); if (activeRequest != null - && activeRequest.getRequestedState() == newState.getIdentifier()) { + && activeRequest.getRequestedStateIdentifier() == newState.getIdentifier()) { ProcessRecord processRecord = mProcessRecords.get(activeRequest.getPid()); if (processRecord != null) { processRecord.notifyRequestActiveAsync(activeRequest.getToken()); @@ -666,21 +668,21 @@ public final class DeviceStateManagerService extends SystemService { case STATUS_ACTIVE: mActiveOverride = Optional.of(request); mDeviceStateNotificationController.showStateActiveNotificationIfNeeded( - request.getRequestedState(), request.getUid()); + request.getRequestedStateIdentifier(), request.getUid()); break; case STATUS_CANCELED: if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { mActiveOverride = Optional.empty(); mDeviceStateNotificationController.cancelNotification( - request.getRequestedState()); + request.getRequestedStateIdentifier()); if ((flags & FLAG_THERMAL_CRITICAL) == FLAG_THERMAL_CRITICAL) { mDeviceStateNotificationController .showThermalCriticalNotificationIfNeeded( - request.getRequestedState()); + request.getRequestedStateIdentifier()); } else if ((flags & FLAG_POWER_SAVE_ENABLED) == FLAG_POWER_SAVE_ENABLED) { mDeviceStateNotificationController .showPowerSaveNotificationIfNeeded( - request.getRequestedState()); + request.getRequestedStateIdentifier()); } } break; @@ -723,7 +725,7 @@ public final class DeviceStateManagerService extends SystemService { */ @GuardedBy("mLock") private void enableBaseStateRequestLocked(OverrideRequest request) { - setBaseState(request.getRequestedState()); + setBaseState(request.getRequestedStateIdentifier()); mActiveBaseStateOverride = Optional.of(request); ProcessRecord processRecord = mProcessRecords.get(request.getPid()); processRecord.notifyRequestActiveAsync(request.getToken()); @@ -762,6 +764,11 @@ public final class DeviceStateManagerService extends SystemService { synchronized (mLock) { mProcessRecords.remove(processRecord.mPid); mOverrideRequestController.handleProcessDied(processRecord.mPid); + + if (shouldCancelOverrideRequestWhenRequesterNotOnTop()) { + OverrideRequest request = mActiveOverride.get(); + mOverrideRequestController.cancelRequest(request); + } } } @@ -787,7 +794,7 @@ public final class DeviceStateManagerService extends SystemService { } OverrideRequest request = new OverrideRequest(token, callingPid, callingUid, - state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay if (!hasControlDeviceStatePermission && mRearDisplayState != null @@ -848,7 +855,7 @@ public final class DeviceStateManagerService extends SystemService { } OverrideRequest request = new OverrideRequest(token, callingPid, callingUid, - state, flags, OVERRIDE_REQUEST_TYPE_BASE_STATE); + deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_BASE_STATE); mOverrideRequestController.addBaseStateRequest(request); } } @@ -1318,7 +1325,7 @@ public final class DeviceStateManagerService extends SystemService { if (mActiveOverride.isEmpty()) { return false; } - int identifier = mActiveOverride.get().getRequestedState(); + int identifier = mActiveOverride.get().getRequestedStateIdentifier(); DeviceState deviceState = mDeviceStates.get(identifier); return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP); } diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java index 74cf184e826e..20485c1ac102 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequest.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java @@ -17,6 +17,7 @@ package com.android.server.devicestate; import android.annotation.IntDef; +import android.annotation.NonNull; import android.hardware.devicestate.DeviceStateRequest; import android.os.IBinder; @@ -32,7 +33,8 @@ final class OverrideRequest { private final IBinder mToken; private final int mPid; private final int mUid; - private final int mRequestedState; + @NonNull + private final DeviceState mRequestedState; @DeviceStateRequest.RequestFlags private final int mFlags; @OverrideRequestType @@ -69,7 +71,7 @@ final class OverrideRequest { @Retention(RetentionPolicy.SOURCE) public @interface OverrideRequestType {} - OverrideRequest(IBinder token, int pid, int uid, int requestedState, + OverrideRequest(IBinder token, int pid, int uid, @NonNull DeviceState requestedState, @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) { mToken = token; mPid = pid; @@ -91,10 +93,15 @@ final class OverrideRequest { return mUid; } - int getRequestedState() { + @NonNull + DeviceState getRequestedDeviceState() { return mRequestedState; } + int getRequestedStateIdentifier() { + return mRequestedState.getIdentifier(); + } + @DeviceStateRequest.RequestFlags int getFlags() { return mFlags; diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index 46f0bc0d9805..f5f2fa8cabdc 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -204,6 +204,12 @@ final class OverrideRequestController { } if (mRequest != null && mRequest.getPid() == pid) { + if (mRequest.getRequestedDeviceState().hasFlag( + DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)) { + cancelCurrentRequestLocked(); + return; + } + if (mStickyRequestsAllowed) { // Do not cancel the requests now because sticky requests are allowed. These // requests will be cancelled on a call to cancelStickyRequests(). @@ -219,7 +225,7 @@ final class OverrideRequestController { * listener of all changes to request status as a result of this change. */ void handleBaseStateChanged(int state) { - if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedState()) { + if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedStateIdentifier()) { cancelBaseStateOverrideRequest(); } if (mRequest == null) { @@ -246,11 +252,12 @@ final class OverrideRequestController { flags |= isThermalCritical ? FLAG_THERMAL_CRITICAL : 0; flags |= isPowerSaveEnabled ? FLAG_POWER_SAVE_ENABLED : 0; if (mBaseStateRequest != null && !contains(newSupportedStates, - mBaseStateRequest.getRequestedState())) { + mBaseStateRequest.getRequestedStateIdentifier())) { cancelCurrentBaseStateRequestLocked(flags); } - if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) { + if (mRequest != null && !contains(newSupportedStates, + mRequest.getRequestedStateIdentifier())) { cancelCurrentRequestLocked(flags); } } @@ -262,7 +269,7 @@ final class OverrideRequestController { pw.println("Override Request active: " + requestActive); if (requestActive) { pw.println("Request: mPid=" + overrideRequest.getPid() - + ", mRequestedState=" + overrideRequest.getRequestedState() + + ", mRequestedState=" + overrideRequest.getRequestedStateIdentifier() + ", mFlags=" + overrideRequest.getFlags() + ", mStatus=" + statusToString(STATUS_ACTIVE)); } diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 3529b048bd34..b1b1dbaf988c 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -122,11 +122,10 @@ final class DisplayDeviceInfo { /** * Flag: This flag identifies secondary displays that should show system decorations, such as - * status bar, navigation bar, home activity or IME. + * navigation bar, home activity or wallpaper. * <p>Note that this flag doesn't work without {@link #FLAG_TRUSTED}</p> * @hide */ - // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 12; /** diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 168715713f8d..5831b29437dc 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -194,10 +194,14 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { @Override public void onComplete(int result) { - if (result != HdmiControlManager.RESULT_SUCCESS) { + if (!mService.getLocalActiveSource().isValid() + && result != HdmiControlManager.RESULT_SUCCESS) { mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( getDeviceInfo().getLogicalAddress(), getDeviceInfo().getPhysicalAddress())); + updateActiveSource(getDeviceInfo().getLogicalAddress(), + getDeviceInfo().getPhysicalAddress(), + "RequestActiveSourceAction#finishWithCallback()"); } } })); @@ -257,6 +261,7 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) { return; } + removeAction(RequestActiveSourceAction.class); if (targetAddress == Constants.ADDR_INTERNAL) { handleSelectInternalSource(); // Switching to internal source is always successful even when CEC control is disabled. diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 3fc9594965a2..972f85793556 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -3504,6 +3504,13 @@ public class InputManagerService extends IInputManager.Stub wm.addView(view, lp); } + /** + * Sets Accessibility bounce keys threshold in milliseconds. + */ + public void setAccessibilityBounceKeysThreshold(int thresholdTimeMs) { + mNative.setAccessibilityBounceKeysThreshold(thresholdTimeMs); + } + interface KeyboardBacklightControllerInterface { default void incrementKeyboardBacklight(int deviceId) {} default void decrementKeyboardBacklight(int deviceId) {} diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index 8e0289ef1b43..0012eab11277 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -85,7 +85,9 @@ class InputSettingsObserver extends ContentObserver { Map.entry(Settings.Secure.getUriFor(Settings.Secure.KEY_REPEAT_DELAY_MS), (reason) -> updateKeyRepeatInfo()), Map.entry(Settings.System.getUriFor(Settings.System.SHOW_ROTARY_INPUT), - (reason) -> updateShowRotaryInput())); + (reason) -> updateShowRotaryInput()), + Map.entry(Settings.System.getUriFor(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS), + (reason) -> updateAccessibilityBounceKeys())); } /** @@ -216,4 +218,9 @@ class InputSettingsObserver extends ContentObserver { } mNative.setMaximumObscuringOpacityForTouch(opacity); } + + private void updateAccessibilityBounceKeys() { + mService.setAccessibilityBounceKeysThreshold( + InputSettings.getAccessibilityBounceKeysThreshold(mContext)); + } } diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 620cde59fb52..49bbe9a7655c 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -246,6 +246,11 @@ interface NativeInputManagerService { */ void sysfsNodeChanged(String sysfsNodePath); + /** + * Notify if Accessibility bounce keys threshold is changed from InputSettings. + */ + void setAccessibilityBounceKeysThreshold(int thresholdTimeMs); + /** The native implementation of InputManagerService methods. */ class NativeImpl implements NativeInputManagerService { /** Pointer to native input manager service object, used by native code. */ @@ -500,5 +505,8 @@ interface NativeInputManagerService { @Override public native void sysfsNodeChanged(String sysfsNodePath); + + @Override + public native void setAccessibilityBounceKeysThreshold(int thresholdTimeMs); } } diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java index dcb86a7d1eb6..66807aeb6629 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java @@ -23,7 +23,9 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.UiThread; import android.content.ComponentName; +import android.content.Context; import android.content.pm.PackageManagerInternal; +import android.hardware.input.InputManager; import android.hardware.input.InputManagerGlobal; import android.os.Handler; import android.os.IBinder; @@ -64,6 +66,7 @@ final class HandwritingModeController { private static final int LONG_EVENT_BUFFER_SIZE = EVENT_BUFFER_SIZE * 20; private static final long HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS = 3000; + private final Context mContext; // This must be the looper for the UiThread. private final Looper mLooper; private final InputManagerInternal mInputManagerInternal; @@ -87,7 +90,9 @@ final class HandwritingModeController { private int mCurrentRequestId; @AnyThread - HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable) { + HandwritingModeController(Context context, Looper uiThreadLooper, + Runnable inkWindowInitRunnable) { + mContext = context; mLooper = uiThreadLooper; mCurrentDisplayId = Display.INVALID_DISPLAY; mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); @@ -285,7 +290,14 @@ final class HandwritingModeController { mHandwritingSurface.startIntercepting(imePid, imeUid); // Unset the pointer icon for the stylus in case the app had set it. - InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED); + if (com.android.input.flags.Flags.enablePointerChoreographer()) { + Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon( + PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED), + downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0), + mHandwritingSurface.getInputChannel().getToken()); + } else { + InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED); + } return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(), mHandwritingBuffer); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index f526dbe9c66d..4089a81dfc20 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -215,10 +215,20 @@ public abstract class InputMethodManagerInternal { /** * Switch the keyboard layout in response to a keyboard shortcut. * - * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the - * previous subtype + * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the + * previous subtype + * @param displayId the display to which the keyboard layout switch shortcut is + * dispatched. Note that there is no guarantee that an IME is + * associated with this display. This is more or less than a hint for + * cases when no IME is running for the given targetWindowToken. There + * is a longstanding discussion whether we should allow users to + * rotate keyboard layout even when there is no edit field, and this + * displayID would be helpful for such a situation. + * @param targetWindowToken the window token to which other keys are being sent while handling + * this shortcut. */ - public abstract void switchKeyboardLayout(int direction); + public abstract void onSwitchKeyboardLayoutShortcut(int direction, int displayId, + IBinder targetWindowToken); /** * Returns true if any InputConnection is currently active. @@ -314,7 +324,8 @@ public abstract class InputMethodManagerInternal { } @Override - public void switchKeyboardLayout(int direction) { + public void onSwitchKeyboardLayoutShortcut(int direction, int displayId, + IBinder targetWindowToken) { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 09c388f08c7b..c440a6461de2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1713,7 +1713,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); mNonPreemptibleInputMethods = mRes.getStringArray( com.android.internal.R.array.config_nonPreemptibleInputMethods); - mHwController = new HandwritingModeController(thread.getLooper(), + mHwController = new HandwritingModeController(mContext, thread.getLooper(), new InkWindowInitializer()); registerDeviceListenerAndCheckStylusSupport(); } @@ -5275,7 +5275,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return true; } } - mSettings.appendAndPutEnabledInputMethodLocked(id, false); + mSettings.appendAndPutEnabledInputMethodLocked(id); // Previous state was disabled. return false; } else { @@ -5612,7 +5612,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (enabled) { if (!settings.getEnabledInputMethodListLocked().contains( methodMap.get(imeId))) { - settings.appendAndPutEnabledInputMethodLocked(imeId, false); + settings.appendAndPutEnabledInputMethodLocked(imeId); } } else { settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( @@ -5626,7 +5626,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) { // TODO(b/287269288): validate that id belongs to a valid virtual device instead. - Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT, + Preconditions.checkArgument(deviceId != Context.DEVICE_ID_DEFAULT, "DeviceId " + deviceId + " does not belong to a virtual device."); synchronized (ImfLock.class) { if (imeId == null) { @@ -5763,7 +5763,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void switchKeyboardLayout(int direction) { + public void onSwitchKeyboardLayoutShortcut(int direction, int displayId, + IBinder targetWindowToken) { synchronized (ImfLock.class) { switchKeyboardLayoutLocked(direction); } @@ -6357,7 +6358,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } if (!previouslyEnabled) { - settings.appendAndPutEnabledInputMethodLocked(imeId, false); + settings.appendAndPutEnabledInputMethodLocked(imeId); } } } else { @@ -6501,7 +6502,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub settings.putEnabledInputMethodsStr(""); nextEnabledImes.forEach( imi -> settings.appendAndPutEnabledInputMethodLocked( - imi.getId(), false)); + imi.getId())); // Reset selected IME. settings.putSelectedInputMethod(nextIme); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index acf9a7f9ae63..2128356e69c6 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -362,10 +362,7 @@ final class InputMethodUtils { return result; } - void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { - if (reloadInputMethodStr) { - getEnabledInputMethodsStr(); - } + void appendAndPutEnabledInputMethodLocked(String id) { if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { // Add in the newly enabled input method. putEnabledInputMethodsStr(id); diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index ec858ee296e1..f2dcba45e312 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -59,6 +59,7 @@ import android.util.apk.SourceStampVerifier; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; @@ -67,7 +68,6 @@ import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.RuleMetadata; import com.android.server.pm.PackageManagerServiceUtils; import com.android.server.pm.parsing.PackageParser2; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.ByteArrayInputStream; import java.io.File; diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index bab3cbea108e..9c27c22dfd00 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -18,6 +18,7 @@ package com.android.server.location.contexthub; import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.contexthub.HostEndpointInfo; +import android.hardware.contexthub.MessageDeliveryStatus; import android.hardware.contexthub.NanSessionRequest; import android.hardware.contexthub.V1_0.ContextHub; import android.hardware.contexthub.V1_0.ContextHubMsg; @@ -467,6 +468,11 @@ public abstract class IContextHubWrapper { // TODO(271471342): Implement } + public void handleMessageDeliveryStatus(char hostEndPointId, + MessageDeliveryStatus messageDeliveryStatus) { + // TODO(b/312417087): Implement reliable message support + } + public byte[] getUuid() { return UUID; } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 42c2548264d4..0c2eee5aebbe 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -209,7 +209,7 @@ import javax.crypto.spec.GCMParameterSpec; * <li>Protect each user's data using their SP. For example, use the SP to encrypt/decrypt the * user's credential-encrypted (CE) key for file-based encryption (FBE).</li> * - * <li>Generate, protect, and use profile passwords for managed profiles.</li> + * <li>Generate, protect, and use unified profile passwords.</li> * * <li>Support unlocking the SP by alternative means: resume-on-reboot (reboot escrow) for easier * OTA updates, and escrow tokens when set up by the Device Policy Controller (DPC).</li> @@ -287,7 +287,7 @@ public class LockSettingsService extends ILockSettings.Stub { private final java.security.KeyStore mJavaKeyStore; private final RecoverableKeyStoreManager mRecoverableKeyStoreManager; - private ManagedProfilePasswordCache mManagedProfilePasswordCache; + private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache; private final RebootEscrowManager mRebootEscrowManager; @@ -404,7 +404,8 @@ public class LockSettingsService extends ILockSettings.Stub { for (int i = 0; i < newPasswordChars.length; i++) { newPassword[i] = (byte) newPasswordChars[i]; } - LockscreenCredential credential = LockscreenCredential.createManagedPassword(newPassword); + LockscreenCredential credential = + LockscreenCredential.createUnifiedProfilePassword(newPassword); Arrays.fill(newPasswordChars, '\u0000'); Arrays.fill(newPassword, (byte) 0); Arrays.fill(randomLockSeed, (byte) 0); @@ -424,7 +425,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (!isCredentialSharableWithParent(profileUserId)) { return; } - // Do not tie profile when work challenge is enabled + // Do not tie profile when separate challenge is enabled if (getSeparateProfileChallengeEnabledInternal(profileUserId)) { return; } @@ -462,7 +463,7 @@ public class LockSettingsService extends ILockSettings.Stub { setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId, /* isLockTiedToParent= */ true); tieProfileLockToParent(profileUserId, parent.id, unifiedProfilePassword); - mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword, + mUnifiedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword, parentSid); } } @@ -620,9 +621,9 @@ public class LockSettingsService extends ILockSettings.Stub { } } - public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache( + public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache( java.security.KeyStore ks) { - return new ManagedProfilePasswordCache(ks); + return new UnifiedProfilePasswordCache(ks); } public boolean isHeadlessSystemUserMode() { @@ -665,7 +666,7 @@ public class LockSettingsService extends ILockSettings.Stub { mGatekeeperPasswords = new LongSparseArray<>(); mSpManager = injector.getSyntheticPasswordManager(mStorage); - mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore); + mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mJavaKeyStore); mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler); mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(), @@ -689,8 +690,8 @@ public class LockSettingsService extends ILockSettings.Stub { } /** - * If the account is credential-encrypted, show notification requesting the user to unlock the - * device. + * If the user is a managed profile whose credential-encrypted storage is locked, show a + * notification requesting the user to unlock the device. */ private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId, String reason) { final UserInfo user = mUserManager.getUserInfo(userId); @@ -846,7 +847,7 @@ public class LockSettingsService extends ILockSettings.Stub { mHandler.post(new Runnable() { @Override public void run() { - // Hide notification first, as tie managed profile lock takes time + // Hide notification first, as tie profile lock takes time hideEncryptionNotification(new UserHandle(userId)); if (isCredentialSharableWithParent(userId)) { @@ -1458,13 +1459,13 @@ public class LockSettingsService extends ILockSettings.Stub { cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv)); decryptionResult = cipher.doFinal(encryptedPassword); - LockscreenCredential credential = LockscreenCredential.createManagedPassword( + LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword( decryptionResult); Arrays.fill(decryptionResult, (byte) 0); try { long parentSid = getGateKeeperService().getSecureUserId( mUserManager.getProfileParent(userId).id); - mManagedProfilePasswordCache.storePassword(userId, credential, parentSid); + mUnifiedProfilePasswordCache.storePassword(userId, credential, parentSid); } catch (RemoteException e) { Slogf.w(TAG, "Failed to talk to GateKeeper service", e); } @@ -1550,7 +1551,7 @@ public class LockSettingsService extends ILockSettings.Stub { // so it goes into the cache getDecryptedPasswordForTiedProfile(profile.id); } catch (GeneralSecurityException | IOException e) { - Slog.d(TAG, "Cache work profile password failed", e); + Slog.d(TAG, "Cache unified profile password failed", e); } } } @@ -1604,19 +1605,19 @@ public class LockSettingsService extends ILockSettings.Stub { } /** - * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them + * Synchronize all profile's challenge of the given user if it's unified: tie or clear them * depending on the parent user's secure state. * - * When clearing tied work challenges, a pre-computed password table for profiles are required, - * since changing password for profiles requires existing password, and existing passwords can - * only be computed before the parent user's password is cleared. + * When clearing tied challenges, a pre-computed password table for profiles are required, since + * changing password for profiles requires existing password, and existing passwords can only be + * computed before the parent user's password is cleared. * * Strictly this is a recursive function, since setLockCredentialInternal ends up calling this * method again on profiles. However the recursion is guaranteed to terminate as this method * terminates when the user is a profile that shares lock credentials with parent. * (e.g. managed and clone profile). */ - private void synchronizeUnifiedWorkChallengeForProfiles(int userId, + private void synchronizeUnifiedChallengeForProfiles(int userId, Map<Integer, LockscreenCredential> profilePasswordMap) { if (isCredentialSharableWithParent(userId)) { return; @@ -1635,7 +1636,7 @@ public class LockSettingsService extends ILockSettings.Stub { tieProfileLockIfNecessary(profileUserId, LockscreenCredential.createNone()); } else { - // We use cached work profile password computed before clearing the parent's + // We use cached profile password computed before clearing the parent's // credential, otherwise they get lost if (profilePasswordMap != null && profilePasswordMap.containsKey(profileUserId)) { @@ -1777,7 +1778,7 @@ public class LockSettingsService extends ILockSettings.Stub { notifyPasswordChanged(credential, userId); } if (isCredentialSharableWithParent(userId)) { - // Make sure the profile doesn't get locked straight after setting work challenge. + // Make sure the profile doesn't get locked straight after setting challenge. setDeviceUnlockedForUser(userId); } notifySeparateProfileChallengeChanged(userId); @@ -2368,7 +2369,7 @@ public class LockSettingsService extends ILockSettings.Stub { } try { - // Unlock work profile, and work profile with unified lock must use password only + // Unlock profile with unified lock return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId), userId, null /* progressCallback */, flags); } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException @@ -2492,7 +2493,7 @@ public class LockSettingsService extends ILockSettings.Stub { mStrongAuth.removeUser(userId); AndroidKeyStoreMaintenance.onUserRemoved(userId); - mManagedProfilePasswordCache.removePassword(userId); + mUnifiedProfilePasswordCache.removePassword(userId); gateKeeperClearSecureUserId(userId); removeKeystoreProfileKey(userId); @@ -2982,7 +2983,7 @@ public class LockSettingsService extends ILockSettings.Stub { credential, sp, userId); final Map<Integer, LockscreenCredential> profilePasswords; if (!credential.isNone()) { - // not needed by synchronizeUnifiedWorkChallengeForProfiles() + // not needed by synchronizeUnifiedChallengeForProfiles() profilePasswords = null; if (!mSpManager.hasSidForUser(userId)) { @@ -2993,8 +2994,8 @@ public class LockSettingsService extends ILockSettings.Stub { } } } else { - // Cache all profile password if they use unified work challenge. This will later be - // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles() + // Cache all profile password if they use unified challenge. This will later be used to + // clear the profile's password in synchronizeUnifiedChallengeForProfiles(). profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId); mSpManager.clearSidForUser(userId); @@ -3010,10 +3011,10 @@ public class LockSettingsService extends ILockSettings.Stub { } setCurrentLskfBasedProtectorId(newProtectorId, userId); LockPatternUtils.invalidateCredentialTypeCache(); - synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords); + synchronizeUnifiedChallengeForProfiles(userId, profilePasswords); setUserPasswordMetrics(credential, userId); - mManagedProfilePasswordCache.removePassword(userId); + mUnifiedProfilePasswordCache.removePassword(userId); if (savedCredentialType != CREDENTIAL_TYPE_NONE) { mSpManager.destroyAllWeakTokenBasedProtectors(userId); } @@ -3114,7 +3115,7 @@ public class LockSettingsService extends ILockSettings.Stub { try { currentCredential = getDecryptedPasswordForTiedProfile(userId); } catch (Exception e) { - Slog.e(TAG, "Failed to get work profile credential", e); + Slog.e(TAG, "Failed to get unified profile password", e); return null; } } @@ -3284,7 +3285,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public boolean tryUnlockWithCachedUnifiedChallenge(int userId) { checkPasswordReadPermission(); - try (LockscreenCredential cred = mManagedProfilePasswordCache.retrievePassword(userId)) { + try (LockscreenCredential cred = mUnifiedProfilePasswordCache.retrievePassword(userId)) { if (cred == null) { return false; } @@ -3296,7 +3297,7 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void removeCachedUnifiedChallenge(int userId) { checkWritePermission(); - mManagedProfilePasswordCache.removePassword(userId); + mUnifiedProfilePasswordCache.removePassword(userId); } static String timestampToString(long timestamp) { diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 1e8b387fc189..6d123ccebc7c 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -501,10 +501,10 @@ class LockSettingsStorage { final UserInfo parentInfo = um.getProfileParent(userId); if (parentInfo == null) { - // This user owns its lock settings files - safe to delete them + // Delete files specific to non-profile users. deleteFile(getRebootEscrowFile(userId)); } else { - // Managed profile + // Delete files specific to profile users. removeChildProfileLock(userId); } diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java index 1298fe8f07a4..21caf76d30d0 100644 --- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java +++ b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java @@ -43,30 +43,31 @@ import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; /** - * Caches *unified* work challenge for managed profiles. The cached credential is encrypted using - * a keystore key auth-bound to the parent user's lockscreen credential, similar to how unified - * work challenge is normally secured. - * - * <p> The cache is filled whenever the managed profile's unified challenge is created or derived - * (as part of the parent user's credential verification flow). It's removed when the profile is - * deleted or a (separate) lockscreen credential is explicitly set on the profile. There is also - * an ADB command to evict the cache "cmd lock_settings remove-cache --user X", to assist - * development and testing. - - * <p> The encrypted credential is stored in-memory only so the cache does not persist across - * reboots. + * An in-memory cache for unified profile passwords. A "unified profile password" is the random + * password that the system automatically generates and manages for each profile that uses a unified + * challenge and where the parent user has a secure lock screen. + * <p> + * Each password in this cache is encrypted by a Keystore key that is auth-bound to the parent user. + * This is very similar to how the password is protected on-disk, but the in-memory cache uses a + * much longer timeout on the keys: 7 days instead of 30 seconds. This enables use cases like + * unpausing work apps without requiring authentication as frequently. + * <p> + * Unified profile passwords are cached when they are created, or when they are decrypted as part of + * the parent user's LSKF verification flow. They are removed when the profile is deleted or when a + * separate challenge is explicitly set on the profile. There is also an ADB command to evict a + * cached password, "locksettings remove-cache --user X", to assist development and testing. */ @VisibleForTesting // public visibility is needed for Mockito -public class ManagedProfilePasswordCache { +public class UnifiedProfilePasswordCache { - private static final String TAG = "ManagedProfilePasswordCache"; + private static final String TAG = "UnifiedProfilePasswordCache"; private static final int KEY_LENGTH = 256; private static final int CACHE_TIMEOUT_SECONDS = (int) TimeUnit.DAYS.toSeconds(7); private final SparseArray<byte[]> mEncryptedPasswords = new SparseArray<>(); private final KeyStore mKeyStore; - public ManagedProfilePasswordCache(KeyStore keyStore) { + public UnifiedProfilePasswordCache(KeyStore keyStore) { mKeyStore = keyStore; } @@ -151,7 +152,8 @@ public class ManagedProfilePasswordCache { Slog.d(TAG, "Cannot decrypt", e); return null; } - LockscreenCredential result = LockscreenCredential.createManagedPassword(credential); + LockscreenCredential result = + LockscreenCredential.createUnifiedProfilePassword(credential); Arrays.fill(credential, (byte) 0); return result; } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 10b6052bec69..e5807e84a70e 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -82,6 +82,7 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -272,9 +273,10 @@ public class RecoverableKeyStoreManager { CertPath certPath; X509Certificate rootCert = mTestCertHelper.getRootCertificate(rootCertificateAlias); + Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias); try { Log.d(TAG, "Getting and validating a random endpoint certificate"); - certPath = certXml.getRandomEndpointCert(rootCert); + certPath = certXml.getRandomEndpointCert(rootCert, validationDate); } catch (CertValidationException e) { Log.e(TAG, "Invalid endpoint cert", e); throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); @@ -348,10 +350,11 @@ public class RecoverableKeyStoreManager { X509Certificate rootCert = mTestCertHelper.getRootCertificate(rootCertificateAlias); + Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias); try { - sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile); + sigXml.verifyFileSignature(rootCert, recoveryServiceCertFile, validationDate); } catch (CertValidationException e) { - Log.d(TAG, "The signature over the cert file is invalid." + Log.e(TAG, "The signature over the cert file is invalid." + " Cert: " + HexDump.toHexString(recoveryServiceCertFile) + " Sig: " + HexDump.toHexString(recoveryServiceSigFile)); throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); @@ -601,8 +604,9 @@ public class RecoverableKeyStoreManager { } try { - CertUtils.validateCertPath( - mTestCertHelper.getRootCertificate(rootCertificateAlias), certPath); + Date validationDate = mTestCertHelper.getValidationDate(rootCertificateAlias); + CertUtils.validateCertPath(mTestCertHelper.getRootCertificate(rootCertificateAlias), + certPath, validationDate); } catch (CertValidationException e) { Log.e(TAG, "Failed to validate the given cert path", e); throw new ServiceSpecificException(ERROR_INVALID_CERTIFICATE, e.getMessage()); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java index c963f799245f..4a1cae2037f9 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java @@ -29,6 +29,7 @@ import android.util.Pair; import com.android.internal.widget.LockPatternUtils; import java.security.cert.X509Certificate; +import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -67,6 +68,18 @@ public class TestOnlyInsecureCertificateHelper { return rootCertificate; } + /** + * Returns hardcoded validation date for e2e tests. + */ + public @Nullable Date getValidationDate(String rootCertificateAlias) { + if (isTestOnlyCertificateAlias(rootCertificateAlias)) { + // Certificate used for e2e test is expired. + return new Date(2019 - 1900, 1, 30); + } else { + return null; // Use current time + } + } + public @NonNull String getDefaultCertificateAliasIfEmpty( @Nullable String rootCertificateAlias) { if (rootCertificateAlias == null || rootCertificateAlias.isEmpty()) { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java index 26e82704b357..088127526d18 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertUtils.java @@ -305,12 +305,13 @@ public final class CertUtils { * * @param trustedRoot the trusted root certificate * @param certPath the certificate path to be validated + * @param validationDate use null for current time * @throws CertValidationException if the given certificate path is invalid, e.g., is expired, * or does not have a valid signature */ - public static void validateCertPath(X509Certificate trustedRoot, CertPath certPath) - throws CertValidationException { - validateCertPath(/*validationDate=*/ null, trustedRoot, certPath); + public static void validateCertPath(X509Certificate trustedRoot, CertPath certPath, + @Nullable Date validationDate) throws CertValidationException { + validateCertPath(validationDate, trustedRoot, certPath); } /** diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java index ff22a8dc934f..d159a84f0468 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/CertXml.java @@ -76,15 +76,16 @@ public final class CertXml { * and returns the certificate path including the chosen certificate if it is valid. * * @param trustedRoot the trusted root certificate + * @param validationDate use null for current time * @return the certificate path including the chosen certificate if the certificate is valid * @throws CertValidationException if the chosen certificate cannot be validated based on the * trusted root certificate */ - public CertPath getRandomEndpointCert(X509Certificate trustedRoot) - throws CertValidationException { + public CertPath getRandomEndpointCert(X509Certificate trustedRoot, + @Nullable Date validationDate)throws CertValidationException { return getEndpointCert( new SecureRandom().nextInt(this.endpointCerts.size()), - /*validationDate=*/ null, + validationDate, trustedRoot); } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java index e75be8574254..c3f4f55b2dd7 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/certificate/SigXml.java @@ -18,7 +18,7 @@ package com.android.server.locksettings.recoverablekeystore.certificate; import android.annotation.Nullable; -import com.android.internal.annotations.VisibleForTesting; +import org.w3c.dom.Element; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -26,8 +26,6 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import org.w3c.dom.Element; - /** * Parses and holds the XML file containing the signature of the XML file containing the list of THM * public-key certificates. @@ -58,17 +56,13 @@ public final class SigXml { * * @param trustedRoot the trusted root certificate * @param signedFileBytes the original file content that has been signed + * @param validationDate use null for current time + * * @throws CertValidationException if the signature verification fails, or the signer's * certificate contained in this XML file cannot be validated * based on the trusted root certificate */ - public void verifyFileSignature(X509Certificate trustedRoot, byte[] signedFileBytes) - throws CertValidationException { - verifyFileSignature(trustedRoot, signedFileBytes, /*validationDate=*/ null); - } - - @VisibleForTesting - void verifyFileSignature( + public void verifyFileSignature( X509Certificate trustedRoot, byte[] signedFileBytes, @Nullable Date validationDate) throws CertValidationException { CertUtils.validateCert(validationDate, trustedRoot, intermediateCerts, signerCert); diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java index a00999d08b5b..7cf3983e57f6 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java @@ -39,6 +39,7 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.media.flags.Flags; import java.util.ArrayList; import java.util.HashMap; @@ -219,7 +220,10 @@ import java.util.stream.Collectors; BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); newBtRoute.mBtDevice = device; - String deviceName = device.getName(); + String deviceName = + Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName() + ? device.getAlias() + : device.getName(); if (TextUtils.isEmpty(deviceName)) { deviceName = mContext.getResources().getText(R.string.unknownName).toString(); } 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/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java index 041fceaf8d3d..ede2d274563e 100644 --- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java @@ -43,6 +43,7 @@ import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.R; +import com.android.media.flags.Flags; import java.util.ArrayList; import java.util.HashMap; @@ -283,7 +284,10 @@ class LegacyBluetoothRouteController implements BluetoothRouteController { newBtRoute.mBtDevice = device; String routeId = device.getAddress(); - String deviceName = device.getName(); + String deviceName = + Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName() + ? device.getAlias() + : device.getName(); if (TextUtils.isEmpty(deviceName)) { deviceName = mContext.getResources().getText(R.string.unknownName).toString(); } diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java index 9fdeda4b4bd0..885566693b9a 100644 --- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java +++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java @@ -16,14 +16,23 @@ package com.android.server.notification; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; + import android.app.UiModeManager; import android.app.WallpaperManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.display.ColorDisplayManager; import android.os.Binder; import android.os.PowerManager; import android.service.notification.DeviceEffectsApplier; import android.service.notification.ZenDeviceEffects; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ConfigChangeOrigin; + +import com.android.internal.annotations.GuardedBy; /** Default implementation for {@link DeviceEffectsApplier}. */ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { @@ -34,13 +43,24 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { private static final int SATURATION_LEVEL_FULL_COLOR = 100; private static final float WALLPAPER_DIM_AMOUNT_DIMMED = 0.6f; private static final float WALLPAPER_DIM_AMOUNT_NORMAL = 0f; + private static final IntentFilter SCREEN_OFF_INTENT_FILTER = new IntentFilter( + Intent.ACTION_SCREEN_OFF); + private final Context mContext; private final ColorDisplayManager mColorDisplayManager; private final PowerManager mPowerManager; private final UiModeManager mUiModeManager; private final WallpaperManager mWallpaperManager; + private final Object mRegisterReceiverLock = new Object(); + @GuardedBy("mRegisterReceiverLock") + private boolean mIsScreenOffReceiverRegistered; + + private ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build(); + private boolean mPendingNightMode; + DefaultDeviceEffectsApplier(Context context) { + mContext = context; mColorDisplayManager = context.getSystemService(ColorDisplayManager.class); mPowerManager = context.getSystemService(PowerManager.class); mUiModeManager = context.getSystemService(UiModeManager.class); @@ -48,24 +68,90 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { } @Override - public void apply(ZenDeviceEffects effects) { + public void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int origin) { Binder.withCleanCallingIdentity(() -> { - mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN, - effects.shouldSuppressAmbientDisplay()); + if (mLastAppliedEffects.shouldSuppressAmbientDisplay() + != effects.shouldSuppressAmbientDisplay()) { + mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN, + effects.shouldSuppressAmbientDisplay()); + } - if (mColorDisplayManager != null) { - mColorDisplayManager.setSaturationLevel( - effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE - : SATURATION_LEVEL_FULL_COLOR); + if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) { + if (mColorDisplayManager != null) { + mColorDisplayManager.setSaturationLevel( + effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE + : SATURATION_LEVEL_FULL_COLOR); + } } - if (mWallpaperManager != null) { - mWallpaperManager.setWallpaperDimAmount( - effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED - : WALLPAPER_DIM_AMOUNT_NORMAL); + if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) { + if (mWallpaperManager != null) { + mWallpaperManager.setWallpaperDimAmount( + effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED + : WALLPAPER_DIM_AMOUNT_NORMAL); + } } - // TODO: b/308673343 - Apply dark theme (via UiModeManager) when screen is off. + if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) { + updateOrScheduleNightMode(effects.shouldUseNightMode(), origin); + } }); + + mLastAppliedEffects = effects; + } + + private void updateOrScheduleNightMode(boolean useNightMode, @ConfigChangeOrigin int origin) { + mPendingNightMode = useNightMode; + + // Changing the theme can be disruptive for the user (Activities are likely recreated, may + // lose some state). Therefore we only apply the change immediately if the rule was + // activated manually, or we are initializing, or the screen is currently off/dreaming. + if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT + || origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER + || origin == ZenModeConfig.UPDATE_ORIGIN_USER + || origin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + || !mPowerManager.isInteractive()) { + unregisterScreenOffReceiver(); + updateNightModeImmediately(useNightMode); + } else { + registerScreenOffReceiver(); + } + } + + @GuardedBy("mRegisterReceiverLock") + private final BroadcastReceiver mNightModeWhenScreenOff = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + unregisterScreenOffReceiver(); + updateNightModeImmediately(mPendingNightMode); + } + }; + + private void updateNightModeImmediately(boolean useNightMode) { + Binder.withCleanCallingIdentity(() -> { + // TODO: b/314285749 - Placeholder; use real APIs when available. + mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME, + useNightMode); + }); + } + + private void registerScreenOffReceiver() { + synchronized (mRegisterReceiverLock) { + if (!mIsScreenOffReceiverRegistered) { + mContext.registerReceiver(mNightModeWhenScreenOff, SCREEN_OFF_INTENT_FILTER, + Context.RECEIVER_NOT_EXPORTED); + mIsScreenOffReceiverRegistered = true; + } + } + } + + private void unregisterScreenOffReceiver() { + synchronized (mRegisterReceiverLock) { + if (mIsScreenOffReceiverRegistered) { + mIsScreenOffReceiverRegistered = false; + mContext.unregisterReceiver(mNightModeWhenScreenOff); + } + } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 02845fb03119..66a974080a43 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.Ranking.R import static android.service.notification.NotificationListenerService.Ranking.RANKING_UNCHANGED; import static android.service.notification.NotificationListenerService.TRIM_FULL; import static android.service.notification.NotificationListenerService.TRIM_LIGHT; +import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; @@ -414,6 +415,12 @@ public class NotificationManagerService extends SystemService { static final long SNOOZE_UNTIL_UNSPECIFIED = -1; + /** + * The threshold, in milliseconds, to determine whether a notification has been + * cleared too quickly. + */ + private static final int NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS = 5000; + static final int INVALID_UID = -1; static final String ROOT_PKG = "root"; @@ -4817,9 +4824,12 @@ public class NotificationManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final long identity = Binder.clearCallingIdentity(); + boolean notificationsRapidlyCleared = false; + final String pkg; try { synchronized (mNotificationLock) { final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + pkg = info.component.getPackageName(); // Cancellation reason. If the token comes from assistant, label the // cancellation as coming from the assistant; default to LISTENER_CANCEL. @@ -4838,11 +4848,19 @@ public class NotificationManagerService extends SystemService { !mUserProfiles.isCurrentProfile(userId)) { continue; } + notificationsRapidlyCleared = notificationsRapidlyCleared + || isNotificationRecent(r); cancelNotificationFromListenerLocked(info, callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), r.getSbn().getId(), userId, reason); } } else { + for (NotificationRecord notificationRecord : mNotificationList) { + if (isNotificationRecent(notificationRecord)) { + notificationsRapidlyCleared = true; + break; + } + } if (lifetimeExtensionRefactor()) { cancelAllLocked(callingUid, callingPid, info.userid, REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(), @@ -4855,11 +4873,23 @@ public class NotificationManagerService extends SystemService { } } } + if (notificationsRapidlyCleared) { + mAppOps.noteOpNoThrow(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, + callingUid, pkg, /* attributionTag= */ null, /* message= */ null); + } } finally { Binder.restoreCallingIdentity(identity); } } + private boolean isNotificationRecent(@NonNull NotificationRecord notificationRecord) { + if (!rapidClearNotificationsByListenerAppOpEnabled()) { + return false; + } + return notificationRecord.getFreshnessMs(System.currentTimeMillis()) + < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS; + } + /** * Handle request from an approved listener to re-enable itself. * @@ -5299,11 +5329,11 @@ public class NotificationManagerService extends SystemService { public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException { enforceSystemOrSystemUI("INotificationManager.setZenMode"); final int callingUid = Binder.getCallingUid(); - final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); final long identity = Binder.clearCallingIdentity(); try { - mZenModeHelper.setManualZenMode(mode, conditionId, null, reason, callingUid, - isSystemOrSystemUi); + mZenModeHelper.setManualZenMode(mode, conditionId, + ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, // Checked by enforce() + reason, /* caller= */ null, callingUid); } finally { Binder.restoreCallingIdentity(identity); } @@ -5334,14 +5364,7 @@ public class NotificationManagerService extends SystemService { @Override public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg) { - Objects.requireNonNull(automaticZenRule, "automaticZenRule is null"); - Objects.requireNonNull(automaticZenRule.getName(), "Name is null"); - if (automaticZenRule.getOwner() == null - && automaticZenRule.getConfigurationActivity() == null) { - throw new NullPointerException( - "Rule must have a conditionproviderservice and/or configuration activity"); - } - Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null"); + validateAutomaticZenRule(automaticZenRule); checkCallerIsSameApp(pkg); if (automaticZenRule.getZenPolicy() != null && automaticZenRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) { @@ -5360,30 +5383,48 @@ public class NotificationManagerService extends SystemService { } return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule, - "addAutomaticZenRule", Binder.getCallingUid(), - // TODO: b/308670715: Distinguish FROM_APP from FROM_USER - isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI - : ZenModeHelper.FROM_APP); + // TODO: b/308670715: Distinguish origin properly (e.g. USER if creating a rule + // manually in Settings). + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + "addAutomaticZenRule", Binder.getCallingUid()); } @Override - public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) - throws RemoteException { - Objects.requireNonNull(automaticZenRule, "automaticZenRule is null"); - Objects.requireNonNull(automaticZenRule.getName(), "Name is null"); - if (automaticZenRule.getOwner() == null - && automaticZenRule.getConfigurationActivity() == null) { + public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) { + validateAutomaticZenRule(automaticZenRule); + enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule"); + + // TODO: b/308670715: Distinguish origin properly (e.g. USER if updating a rule + // manually in Settings). + return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule, + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + "updateAutomaticZenRule", Binder.getCallingUid()); + } + + private void validateAutomaticZenRule(AutomaticZenRule rule) { + Objects.requireNonNull(rule, "automaticZenRule is null"); + Objects.requireNonNull(rule.getName(), "Name is null"); + rule.validate(); + if (rule.getOwner() == null + && rule.getConfigurationActivity() == null) { throw new NullPointerException( "Rule must have a conditionproviderservice and/or configuration activity"); } - Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null"); - enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule"); + Objects.requireNonNull(rule.getConditionId(), "ConditionId is null"); - return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule, - "updateAutomaticZenRule", Binder.getCallingUid(), - // TODO: b/308670715: Distinguish FROM_APP from FROM_USER - isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI - : ZenModeHelper.FROM_APP); + if (android.app.Flags.modesApi()) { + if (rule.getType() == AutomaticZenRule.TYPE_MANAGED) { + int uid = Binder.getCallingUid(); + boolean isDeviceOwner = Binder.withCleanCallingIdentity( + () -> mDpm.isActiveDeviceOwner(uid)); + if (!isDeviceOwner) { + throw new IllegalArgumentException( + "Only Device Owners can use AutomaticZenRules with TYPE_MANAGED"); + } + } + } } @Override @@ -5392,8 +5433,12 @@ public class NotificationManagerService extends SystemService { // Verify that they can modify zen rules. enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule"); - return mZenModeHelper.removeAutomaticZenRule(id, "removeAutomaticZenRule", - Binder.getCallingUid(), isCallerSystemOrSystemUi()); + // TODO: b/308670715: Distinguish origin properly (e.g. USER if removing a rule + // manually in Settings). + return mZenModeHelper.removeAutomaticZenRule(id, + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + "removeAutomaticZenRule", Binder.getCallingUid()); } @Override @@ -5402,8 +5447,9 @@ public class NotificationManagerService extends SystemService { enforceSystemOrSystemUI("removeAutomaticZenRules"); return mZenModeHelper.removeAutomaticZenRules(packageName, - packageName + "|removeAutomaticZenRules", Binder.getCallingUid(), - isCallerSystemOrSystemUi()); + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + packageName + "|removeAutomaticZenRules", Binder.getCallingUid()); } @Override @@ -5418,11 +5464,16 @@ public class NotificationManagerService extends SystemService { public void setAutomaticZenRuleState(String id, Condition condition) { Objects.requireNonNull(id, "id is null"); Objects.requireNonNull(condition, "Condition is null"); + condition.validate(); enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState"); - mZenModeHelper.setAutomaticZenRuleState(id, condition, Binder.getCallingUid(), - isCallerSystemOrSystemUi()); + // TODO: b/308670715: Distinguish origin properly (e.g. USER if toggling a rule + // manually in Settings). + mZenModeHelper.setAutomaticZenRuleState(id, condition, + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + Binder.getCallingUid()); } @Override @@ -5440,8 +5491,11 @@ public class NotificationManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { - mZenModeHelper.setManualZenMode(zen, null, pkg, "setInterruptionFilter", - callingUid, isSystemOrSystemUi); + mZenModeHelper.setManualZenMode(zen, null, + isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + /* reason= */ "setInterruptionFilter", /* caller= */ pkg, + callingUid); } finally { Binder.restoreCallingIdentity(identity); } @@ -5806,7 +5860,10 @@ public class NotificationManagerService extends SystemService { } else { ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy); - mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi); + mZenModeHelper.setNotificationPolicy(policy, + isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + callingUid); } } catch (RemoteException e) { Slog.e(TAG, "Failed to set notification policy", e); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 6a7eebb32c8b..1bafcfe94267 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -59,6 +59,7 @@ import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; import android.service.notification.RankingHelperProto; +import android.service.notification.ZenModeConfig; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -160,7 +161,6 @@ public class PreferencesHelper implements RankingConfig { static final boolean DEFAULT_BUBBLES_ENABLED = true; @VisibleForTesting static final int DEFAULT_BUBBLE_PREFERENCE = BUBBLE_PREFERENCE_NONE; - static final boolean DEFAULT_MEDIA_NOTIFICATION_FILTERING = true; private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_APP = 0; private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER = 1; @@ -199,7 +199,7 @@ public class PreferencesHelper implements RankingConfig { private SparseBooleanArray mBubblesEnabled; private SparseBooleanArray mLockScreenShowNotifications; private SparseBooleanArray mLockScreenPrivateNotifications; - private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING; + private boolean mIsMediaNotificationFilteringEnabled; // When modes_api flag is enabled, this value only tracks whether the current user has any // channels marked as "priority channels", but not necessarily whether they are permitted // to bypass DND by current zen policy. @@ -223,6 +223,8 @@ public class PreferencesHelper implements RankingConfig { mAppOps = appOpsManager; mUserProfiles = userProfiles; mShowReviewPermissionsNotification = showReviewPermissionsNotification; + mIsMediaNotificationFilteringEnabled = context.getResources() + .getBoolean(R.bool.config_quickSettingsShowMediaPlayer); XML_VERSION = 4; @@ -1862,12 +1864,16 @@ public class PreferencesHelper implements RankingConfig { public void updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid, boolean fromSystemOrSystemUi) { NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy(); - mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy( - policy.priorityCategories, policy.priorityCallSenders, - policy.priorityMessageSenders, policy.suppressedVisualEffects, - (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND - : 0), - policy.priorityConversationSenders), callingUid, fromSystemOrSystemUi); + mZenModeHelper.setNotificationPolicy( + new NotificationManager.Policy( + policy.priorityCategories, policy.priorityCallSenders, + policy.priorityMessageSenders, policy.suppressedVisualEffects, + (areChannelsBypassingDnd + ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0), + policy.priorityConversationSenders), + fromSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + callingUid); } // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined. @@ -2687,8 +2693,11 @@ public class PreferencesHelper implements RankingConfig { /** Requests check of the feature setting for showing media notifications in quick settings. */ public void updateMediaNotificationFilteringEnabled() { + // TODO(b/192412820): Consolidate SHOW_MEDIA_ON_QUICK_SETTINGS into compile-time value. final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) > 0; + Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) > 0 + && mContext.getResources().getBoolean( + R.bool.config_quickSettingsShowMediaPlayer); if (newValue != mIsMediaNotificationFilteringEnabled) { mIsMediaNotificationFilteringEnabled = newValue; updateConfig(); diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index 6ecd799bf8e6..86aa2d8b0a10 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -111,8 +111,10 @@ public class ZenModeConditions implements ConditionProviders.Callback { public void onServiceAdded(ComponentName component) { if (DEBUG) Log.d(TAG, "onServiceAdded " + component); final int callingUid = Binder.getCallingUid(); - mHelper.setConfig(mHelper.getConfig(), component, "zmc.onServiceAdded:" + component, - callingUid, callingUid == Process.SYSTEM_UID); + mHelper.setConfig(mHelper.getConfig(), component, + callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + "zmc.onServiceAdded:" + component, callingUid); } @Override @@ -121,8 +123,10 @@ public class ZenModeConditions implements ConditionProviders.Callback { ZenModeConfig config = mHelper.getConfig(); if (config == null) return; final int callingUid = Binder.getCallingUid(); - mHelper.setAutomaticZenRuleState(id, condition, callingUid, - callingUid == Process.SYSTEM_UID); + mHelper.setAutomaticZenRuleState(id, condition, + callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + callingUid); } // Only valid for CPS backed rules diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 218519fef68b..3f8b5952a1bc 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2014, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,11 +24,16 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.service.notification.NotificationServiceProto.ROOT_CONFIG; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import android.annotation.DrawableRes; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -79,6 +84,7 @@ import android.service.notification.ConditionProviderService; import android.service.notification.DeviceEffectsApplier; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ConfigChangeOrigin; import android.service.notification.ZenModeConfig.ZenRule; import android.service.notification.ZenModeProto; import android.service.notification.ZenPolicy; @@ -111,8 +117,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -137,21 +141,6 @@ public class ZenModeHelper { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L; - /** A rule addition or update that is initiated by the System or SystemUI. */ - static final int FROM_SYSTEM_OR_SYSTEMUI = 1; - /** A rule addition or update that is initiated by the user (through system settings). */ - static final int FROM_USER = 2; - /** A rule addition or update that is initiated by an app (via NotificationManager APIs). */ - static final int FROM_APP = 3; - - @IntDef(prefix = { "FROM_" }, value = { - FROM_SYSTEM_OR_SYSTEMUI, - FROM_USER, - FROM_APP - }) - @Retention(RetentionPolicy.SOURCE) - @interface ChangeOrigin {} - // pkg|userId => uid @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>(); @@ -160,7 +149,7 @@ public class ZenModeHelper { private final SettingsObserver mSettingsObserver; private final AppOpsManager mAppOps; private final NotificationManager mNotificationManager; - private ZenModeConfig mDefaultConfig; + private final ZenModeConfig mDefaultConfig; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final ZenModeFiltering mFiltering; private final RingerModeDelegate mRingerModeDelegate = new @@ -275,9 +264,8 @@ public class ZenModeHelper { // "update" config to itself, which will have no effect in the case where a config // was read in via XML, but will initialize zen mode if nothing was read in and the // config remains the default. - updateConfigAndZenModeLocked(mConfig, "init", true /*setRingerMode*/, - Process.SYSTEM_UID /* callingUid */, true /* is system */, - false /* no broadcasts*/); + updateConfigAndZenModeLocked(mConfig, UPDATE_ORIGIN_INIT, "init", + true /*setRingerMode*/, Process.SYSTEM_UID /* callingUid */); } } @@ -297,24 +285,20 @@ public class ZenModeHelper { /** * Set the {@link DeviceEffectsApplier} used to apply the consolidated effects. * - * <p>If effects were calculated previously (for example, when we loaded a {@link ZenModeConfig} - * that includes activated rules), they will be applied immediately. + * <p>Previously calculated effects (as loaded from the user's {@link ZenModeConfig}) will be + * applied immediately. */ void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) { if (!Flags.modesApi()) { return; } - ZenDeviceEffects consolidatedDeviceEffects; synchronized (mConfigLock) { if (mDeviceEffectsApplier != null) { throw new IllegalStateException("Already set up a DeviceEffectsApplier!"); } mDeviceEffectsApplier = deviceEffectsApplier; - consolidatedDeviceEffects = mConsolidatedDeviceEffects; - } - if (consolidatedDeviceEffects.hasEffects()) { - applyConsolidatedDeviceEffects(); } + applyConsolidatedDeviceEffects(UPDATE_ORIGIN_INIT); } public void onUserSwitched(int user) { @@ -353,7 +337,8 @@ public class ZenModeHelper { config.user = user; } synchronized (mConfigLock) { - setConfigLocked(config, null, reason, Process.SYSTEM_UID, true); + setConfigLocked(config, null, UPDATE_ORIGIN_INIT_USER, reason, + Process.SYSTEM_UID); } cleanUpZenRules(); } @@ -366,9 +351,11 @@ public class ZenModeHelper { boolean fromSystemOrSystemUi) { final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1); if (newZen != -1) { - setManualZenMode(newZen, null, name != null ? name.getPackageName() : null, - "listener:" + (name != null ? name.flattenToShortString() : null), - callingUid, fromSystemOrSystemUi); + setManualZenMode(newZen, null, + fromSystemOrSystemUi ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : UPDATE_ORIGIN_APP, + /* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null), + /* caller= */ name != null ? name.getPackageName() : null, + callingUid); } } @@ -428,7 +415,7 @@ public class ZenModeHelper { } public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule, - String reason, int callingUid, @ChangeOrigin int origin) { + @ConfigChangeOrigin int origin, String reason, int callingUid) { if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) { PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner()); if (component == null) { @@ -462,10 +449,9 @@ public class ZenModeHelper { } newConfig = mConfig.copy(); ZenRule rule = new ZenRule(); - populateZenRule(pkg, automaticZenRule, rule, true, origin); + populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true); newConfig.automaticRules.put(rule.id, rule); - if (setConfigLocked(newConfig, reason, rule.component, true, callingUid, - origin == FROM_SYSTEM_OR_SYSTEMUI)) { + if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) { return rule.id; } else { throw new AndroidRuntimeException("Could not create rule"); @@ -474,7 +460,7 @@ public class ZenModeHelper { } public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule, - String reason, int callingUid, @ChangeOrigin int origin) { + @ConfigChangeOrigin int origin, String reason, int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -502,9 +488,9 @@ public class ZenModeHelper { } } - populateZenRule(rule.pkg, automaticZenRule, rule, false, origin); - return setConfigLocked(newConfig, reason, rule.component, true, callingUid, - origin == FROM_SYSTEM_OR_SYSTEMUI); + populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false); + return setConfigLocked(newConfig, origin, reason, + rule.component, true, callingUid); } } @@ -541,8 +527,7 @@ public class ZenModeHelper { Condition deactivated = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_deactivated), Condition.STATE_FALSE); - setAutomaticZenRuleState(rule.id, deactivated, - callingUid, /* fromSystemOrSystemUi= */ false); + setAutomaticZenRuleState(rule.id, deactivated, UPDATE_ORIGIN_APP, callingUid); } } else { // Either create a new rule with a default ZenPolicy, or update an existing rule's @@ -558,9 +543,8 @@ public class ZenModeHelper { rule.condition = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_activated), Condition.STATE_TRUE); - setConfigLocked(newConfig, /* triggeringComponent= */ null, - "applyGlobalZenModeAsImplicitZenRule", - callingUid, /* fromSystemOrSystemUi= */ false); + setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP, + "applyGlobalZenModeAsImplicitZenRule", callingUid); } } } @@ -595,9 +579,8 @@ public class ZenModeHelper { } // TODO: b/308673679 - Keep user customization of this rule! rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy); - setConfigLocked(newConfig, /* triggeringComponent= */ null, - "applyGlobalPolicyAsImplicitZenRule", - callingUid, /* fromSystemOrSystemUi= */ false); + setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP, + "applyGlobalPolicyAsImplicitZenRule", callingUid); } } @@ -644,6 +627,7 @@ public class ZenModeHelper { try { ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0); rule.name = applicationInfo.loadLabel(mPm).toString(); + rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon); } catch (PackageManager.NameNotFoundException e) { // Should not happen, since it's the app calling us (?) Log.w(TAG, "Package not found for creating implicit zen rule"); @@ -651,6 +635,9 @@ public class ZenModeHelper { } }); + rule.type = AutomaticZenRule.TYPE_OTHER; + rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description, + rule.name); rule.condition = null; rule.conditionId = new Uri.Builder() .scheme(Condition.SCHEME) @@ -669,8 +656,8 @@ public class ZenModeHelper { return "implicit_" + forPackage; } - public boolean removeAutomaticZenRule(String id, String reason, int callingUid, - boolean fromSystemOrSystemUi) { + boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason, + int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -695,13 +682,12 @@ public class ZenModeHelper { } dispatchOnAutomaticRuleStatusChanged( mConfig.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED); - return setConfigLocked(newConfig, reason, null, true, callingUid, - fromSystemOrSystemUi); + return setConfigLocked(newConfig, origin, reason, null, true, callingUid); } } - public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid, - boolean fromSystemOrSystemUi) { + boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin, + String reason, int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -712,13 +698,12 @@ public class ZenModeHelper { newConfig.automaticRules.removeAt(i); } } - return setConfigLocked(newConfig, reason, null, true, callingUid, - fromSystemOrSystemUi); + return setConfigLocked(newConfig, origin, reason, null, true, callingUid); } } - public void setAutomaticZenRuleState(String id, Condition condition, int callingUid, - boolean fromSystemOrSystemUi) { + void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin, + int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -726,13 +711,12 @@ public class ZenModeHelper { newConfig = mConfig.copy(); ArrayList<ZenRule> rules = new ArrayList<>(); rules.add(newConfig.automaticRules.get(id)); - setAutomaticZenRuleStateLocked(newConfig, rules, condition, callingUid, - fromSystemOrSystemUi); + setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid); } } - public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid, - boolean fromSystemOrSystemUi) { + void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, + @ConfigChangeOrigin int origin, int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -740,20 +724,23 @@ public class ZenModeHelper { setAutomaticZenRuleStateLocked(newConfig, findMatchingRules(newConfig, ruleDefinition, condition), - condition, callingUid, fromSystemOrSystemUi); + condition, origin, callingUid); } } @GuardedBy("mConfigLock") private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules, - Condition condition, int callingUid, boolean fromSystemOrSystemUi) { + Condition condition, @ConfigChangeOrigin int origin, int callingUid) { if (rules == null || rules.isEmpty()) return; + if (Flags.modesApi() && condition.source == Condition.SOURCE_USER_ACTION) { + origin = UPDATE_ORIGIN_USER; // Although coming from app, it's actually a user action. + } + for (ZenRule rule : rules) { rule.condition = condition; updateSnoozing(rule); - setConfigLocked(config, rule.component, "conditionChanged", callingUid, - fromSystemOrSystemUi); + setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid); } } @@ -857,7 +844,7 @@ public class ZenModeHelper { // update default rule (if locale changed, name of rule will change) currRule.name = defaultRule.name; updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule), - "locale changed", callingUid, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "locale changed", callingUid); } } } @@ -899,13 +886,12 @@ public class ZenModeHelper { return null; } - @VisibleForTesting void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, - boolean isNew, @ChangeOrigin int origin) { - // TODO: b/308671593,b/311406021 - Handle origins more precisely: - // - FROM_USER can override anything and updates bitmask of user-modified fields; - // - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; - // - FROM_APP can only update if not user-modified. + @ConfigChangeOrigin int origin, boolean isNew) { + // TODO: b/308671593,b/311406021 - Handle origins more precisely: + // - USER can override anything and updates bitmask of user-modified fields; + // - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; + // - APP can only update if not user-modified. if (rule.enabled != automaticZenRule.isEnabled()) { rule.snoozing = false; } @@ -951,12 +937,12 @@ public class ZenModeHelper { */ @Nullable private static ZenDeviceEffects fixZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects, - @Nullable ZenDeviceEffects newEffects, @ChangeOrigin int origin) { + @Nullable ZenDeviceEffects newEffects, @ConfigChangeOrigin int origin) { // TODO: b/308671593,b/311406021 - Handle origins more precisely: - // - FROM_USER can override anything and updates bitmask of user-modified fields; - // - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; - // - FROM_APP can only update if not user-modified. - if (origin == FROM_SYSTEM_OR_SYSTEMUI || origin == FROM_USER) { + // - USER can override anything and updates bitmask of user-modified fields; + // - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; + // - APP can only update if not user-modified. + if (origin != UPDATE_ORIGIN_APP) { return newEffects; } @@ -1033,16 +1019,16 @@ public class ZenModeHelper { : AUTOMATIC_RULE_STATUS_DISABLED); } - public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason, - int callingUid, boolean fromSystemOrSystemUi) { - setManualZenMode(zenMode, conditionId, reason, caller, true /*setRingerMode*/, callingUid, - fromSystemOrSystemUi); + void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin, + String reason, @Nullable String caller, int callingUid) { + setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/, + callingUid); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0); } - private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller, - boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) { + private void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin, + String reason, @Nullable String caller, boolean setRingerMode, int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -1069,8 +1055,7 @@ public class ZenModeHelper { } newConfig.manualRule = newRule; } - setConfigLocked(newConfig, reason, null, setRingerMode, callingUid, - fromSystemOrSystemUi); + setConfigLocked(newConfig, origin, reason, null, setRingerMode, callingUid); } } @@ -1199,7 +1184,9 @@ public class ZenModeHelper { } if (DEBUG) Log.d(TAG, reason); synchronized (mConfigLock) { - setConfigLocked(config, null, reason, Process.SYSTEM_UID, true); + setConfigLocked(config, null, + forRestore ? UPDATE_ORIGIN_RESTORE_BACKUP : UPDATE_ORIGIN_INIT, reason, + Process.SYSTEM_UID); } } } @@ -1233,13 +1220,13 @@ public class ZenModeHelper { /** * Sets the global notification policy used for priority only do not disturb */ - public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) { + public void setNotificationPolicy(Policy policy, @ConfigChangeOrigin int origin, + int callingUid) { synchronized (mConfigLock) { if (policy == null || mConfig == null) return; final ZenModeConfig newConfig = mConfig.copy(); newConfig.applyNotificationPolicy(policy); - setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid, - fromSystemOrSystemUi); + setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid); } } @@ -1264,8 +1251,8 @@ public class ZenModeHelper { } } } - setConfigLocked(newConfig, null, "cleanUpZenRules", Process.SYSTEM_UID, - true); + setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "cleanUpZenRules", + Process.SYSTEM_UID); } } @@ -1287,22 +1274,22 @@ public class ZenModeHelper { @GuardedBy("mConfigLock") private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, - String reason, int callingUid, boolean fromSystemOrSystemUi) { - return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/, - callingUid, fromSystemOrSystemUi); + @ConfigChangeOrigin int origin, String reason, int callingUid) { + return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/, + callingUid); } - public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason, - int callingUid, boolean fromSystemOrSystemUi) { + void setConfig(ZenModeConfig config, ComponentName triggeringComponent, + @ConfigChangeOrigin int origin, String reason, int callingUid) { synchronized (mConfigLock) { - setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi); + setConfigLocked(config, triggeringComponent, origin, reason, callingUid); } } @GuardedBy("mConfigLock") - private boolean setConfigLocked(ZenModeConfig config, String reason, - ComponentName triggeringComponent, boolean setRingerMode, int callingUid, - boolean fromSystemOrSystemUi) { + private boolean setConfigLocked(ZenModeConfig config, @ConfigChangeOrigin int origin, + String reason, ComponentName triggeringComponent, boolean setRingerMode, + int callingUid) { final long identity = Binder.clearCallingIdentity(); try { if (config == null || !config.isValid()) { @@ -1332,8 +1319,7 @@ public class ZenModeHelper { if (policyChanged) { dispatchOnPolicyChanged(); } - updateConfigAndZenModeLocked(config, reason, setRingerMode, callingUid, - fromSystemOrSystemUi, true); + updateConfigAndZenModeLocked(config, origin, reason, setRingerMode, callingUid); mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/); return true; } catch (SecurityException e) { @@ -1349,17 +1335,16 @@ public class ZenModeHelper { * If logging is enabled, will also request logging of the outcome of this change if needed. */ @GuardedBy("mConfigLock") - private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason, - boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi, - boolean sendBroadcasts) { + private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigChangeOrigin int origin, + String reason, boolean setRingerMode, int callingUid) { final boolean logZenModeEvents = mFlagResolver.isEnabled( SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS); // Store (a copy of) all config and zen mode info prior to any changes taking effect ZenModeEventLogger.ZenModeInfo prevInfo = new ZenModeEventLogger.ZenModeInfo( mZenMode, mConfig, mConsolidatedPolicy); if (!config.equals(mConfig)) { - // schedule broadcasts - if (Flags.modesApi() && sendBroadcasts) { + // Schedule broadcasts. Cannot be sent during boot, though. + if (Flags.modesApi() && origin != UPDATE_ORIGIN_INIT) { for (ZenRule rule : config.automaticRules.values()) { ZenRule original = mConfig.automaticRules.get(rule.id); if (original != null) { @@ -1377,15 +1362,19 @@ public class ZenModeHelper { mConfig = config; dispatchOnConfigChanged(); - updateAndApplyConsolidatedPolicyAndDeviceEffects(reason); + updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason); } final String val = Integer.toString(config.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); - evaluateZenModeLocked(reason, setRingerMode); + evaluateZenModeLocked(origin, reason, setRingerMode); // After all changes have occurred, log if requested if (logZenModeEvents) { ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo( mZenMode, mConfig, mConsolidatedPolicy); + boolean fromSystemOrSystemUi = origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + || origin == UPDATE_ORIGIN_INIT + || origin == UPDATE_ORIGIN_INIT_USER + || origin == UPDATE_ORIGIN_RESTORE_BACKUP; mZenModeEventLogger.maybeLogZenChange(prevInfo, newInfo, callingUid, fromSystemOrSystemUi); } @@ -1416,7 +1405,8 @@ public class ZenModeHelper { @VisibleForTesting @GuardedBy("mConfigLock") - protected void evaluateZenModeLocked(String reason, boolean setRingerMode) { + protected void evaluateZenModeLocked(@ConfigChangeOrigin int origin, String reason, + boolean setRingerMode) { if (DEBUG) Log.d(TAG, "evaluateZenMode"); if (mConfig == null) return; final int policyHashBefore = mConsolidatedPolicy == null ? 0 @@ -1426,7 +1416,7 @@ public class ZenModeHelper { ZenLog.traceSetZenMode(zen, reason); mZenMode = zen; setZenModeSetting(mZenMode); - updateAndApplyConsolidatedPolicyAndDeviceEffects(reason); + updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason); boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || ( zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS && policyHashBefore != mConsolidatedPolicy.hashCode())); @@ -1468,6 +1458,7 @@ public class ZenModeHelper { } } + @GuardedBy("mConfigLock") private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) { if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { policy.apply(new ZenPolicy.Builder() @@ -1487,7 +1478,9 @@ public class ZenModeHelper { } } - private void updateAndApplyConsolidatedPolicyAndDeviceEffects(String reason) { + @GuardedBy("mConfigLock") + private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigChangeOrigin int origin, + String reason) { synchronized (mConfigLock) { if (mConfig == null) return; ZenPolicy policy = new ZenPolicy(); @@ -1519,13 +1512,13 @@ public class ZenModeHelper { ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); if (!deviceEffects.equals(mConsolidatedDeviceEffects)) { mConsolidatedDeviceEffects = deviceEffects; - mHandler.postApplyDeviceEffects(); + mHandler.postApplyDeviceEffects(origin); } } } } - private void applyConsolidatedDeviceEffects() { + private void applyConsolidatedDeviceEffects(@ConfigChangeOrigin int source) { if (!Flags.modesApi()) { return; } @@ -1536,7 +1529,7 @@ public class ZenModeHelper { effects = mConsolidatedDeviceEffects; } if (applier != null) { - applier.apply(effects); + applier.apply(effects, source); } } @@ -1801,8 +1794,8 @@ public class ZenModeHelper { } @Override - public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller, - int ringerModeExternal, VolumePolicy policy) { + public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, + @Nullable String caller, int ringerModeExternal, VolumePolicy policy) { final boolean isChange = ringerModeOld != ringerModeNew; int ringerModeExternalOut = ringerModeNew; @@ -1839,8 +1832,9 @@ public class ZenModeHelper { } if (newZen != -1) { - setManualZenMode(newZen, null, "ringerModeInternal", null, - false /*setRingerMode*/, Process.SYSTEM_UID, true); + setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false, + Process.SYSTEM_UID); } if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) { ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller, @@ -1856,8 +1850,8 @@ public class ZenModeHelper { } @Override - public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller, - int ringerModeInternal, VolumePolicy policy) { + public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, + @Nullable String caller, int ringerModeInternal, VolumePolicy policy) { int ringerModeInternalOut = ringerModeNew; final boolean isChange = ringerModeOld != ringerModeNew; final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; @@ -1883,8 +1877,8 @@ public class ZenModeHelper { break; } if (newZen != -1) { - setManualZenMode(newZen, null, "ringerModeExternal", caller, - false /*setRingerMode*/, Process.SYSTEM_UID, true); + setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID); } ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, @@ -2024,11 +2018,9 @@ public class ZenModeHelper { } try { final Resources res = mPm.getResourcesForApplication(packageName); - final String fullName = res.getResourceName(resId); - - return fullName; + return res.getResourceName(resId); } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { - Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName + Slog.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName + ". Resource IDs may change when the application is upgraded, and the system" + " may not be able to find the correct resource."); return null; @@ -2149,9 +2141,9 @@ public class ZenModeHelper { sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger)); } - private void postApplyDeviceEffects() { + private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) { removeMessages(MSG_APPLY_EFFECTS); - sendEmptyMessage(MSG_APPLY_EFFECTS); + sendMessage(obtainMessage(MSG_APPLY_EFFECTS, origin, 0)); } @Override @@ -2168,7 +2160,8 @@ public class ZenModeHelper { updateRingerAndAudio(shouldApplyToRinger); break; case MSG_APPLY_EFFECTS: - applyConsolidatedDeviceEffects(); + @ConfigChangeOrigin int origin = msg.arg1; + applyConsolidatedDeviceEffects(origin); break; } } 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/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java index f985b5b64e21..b9b09fb0e84c 100644 --- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java @@ -754,9 +754,9 @@ public class PersistentDataBlockService extends SystemService { } }; - private PersistentDataBlockManagerInternal mInternalService = - new PersistentDataBlockManagerInternal() { + private InternalService mInternalService = new InternalService(); + private class InternalService implements PersistentDataBlockManagerInternal { @Override public void setFrpCredentialHandle(byte[] handle) { writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE); diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 7b35589ae682..7f58e75e0287 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -837,13 +837,11 @@ public final class BroadcastHelper { } final String removedPackage = packageRemovedInfo.mRemovedPackage; - final int removedAppId = packageRemovedInfo.mRemovedAppId; - final int uid = packageRemovedInfo.mUid; final String installerPackageName = packageRemovedInfo.mInstallerPackageName; final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList; Bundle extras = new Bundle(2); - extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid); + extras.putInt(Intent.EXTRA_UID, packageRemovedInfo.mUid); extras.putBoolean(Intent.EXTRA_REPLACING, true); sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, removedPackage, extras, 0, null /*targetPackage*/, null, null, null, broadcastAllowList, null); @@ -888,8 +886,6 @@ public final class BroadcastHelper { boolean removedBySystem, boolean isArchived) { final String removedPackage = packageRemovedInfo.mRemovedPackage; - final int removedAppId = packageRemovedInfo.mRemovedAppId; - final int uid = packageRemovedInfo.mUid; final String installerPackageName = packageRemovedInfo.mInstallerPackageName; final int[] broadcastUserIds = packageRemovedInfo.mBroadcastUsers; final int[] instantUserIds = packageRemovedInfo.mInstantUserIds; @@ -902,8 +898,7 @@ public final class BroadcastHelper { final boolean isStaticSharedLib = packageRemovedInfo.mIsStaticSharedLib; Bundle extras = new Bundle(); - final int removedUid = removedAppId >= 0 ? removedAppId : uid; - extras.putInt(Intent.EXTRA_UID, removedUid); + extras.putInt(Intent.EXTRA_UID, packageRemovedInfo.mUid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved); extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, isRemovedPackageSystemUpdate); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp); @@ -940,10 +935,10 @@ public final class BroadcastHelper { sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null, null, broadcastUserIds, instantUserIds, broadcastAllowList, null); - packageSender.notifyPackageRemoved(removedPackage, removedUid); + packageSender.notifyPackageRemoved(removedPackage, packageRemovedInfo.mUid); } } - if (removedAppId >= 0) { + if (packageRemovedInfo.mIsAppIdRemoved) { // If a system app's updates are uninstalled the UID is not actually removed. Some // services need to know the package name affected. if (isReplace) { diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index 92d469ccbfac..27f4e11c53ad 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -405,9 +405,19 @@ public interface Computer extends PackageDataSnapshot { boolean isInstallDisabledForPackage(@NonNull String packageName, int uid, @UserIdInt int userId); - @Nullable - List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo, - @PackageManager.PackageInfoFlagsBits long flags, int callingUid, @UserIdInt int userId); + /** + * Returns a Pair that contains a list of packages that depend on the target library and the + * package library dependency information. The List<VersionedPackage> indicates a list of + * packages that depend on the target library, it may be null if no package depends on + * the target library. The List<Boolean> indicates whether each VersionedPackage in + * the List<VersionedPackage> optionally depends on the target library, where true means + * optional and false means required. It may be null if no package depends on + * the target library or without dependency information, e.g. uses-static-library. + */ + @NonNull + Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary( + @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags, + int callingUid, @UserIdInt int userId); @Nullable ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( @@ -467,7 +477,7 @@ public interface Computer extends PackageDataSnapshot { @NonNull List<ApplicationInfo> getInstalledApplications( @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId, - int callingUid); + int callingUid, boolean forceAllowCrossUser); @Nullable ProviderInfo resolveContentProvider(@NonNull String name, @@ -498,12 +508,15 @@ public interface Computer extends PackageDataSnapshot { boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, @UserIdInt int userId); - boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId); + boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException; - boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId); + boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException; /** Check if the package is in a stopped state for a given user. */ - boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId); + boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException; boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 139c7c0849b0..abfd5715810e 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -895,6 +895,9 @@ public class ComputerEngine implements Computer { @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) { ParsedActivity a = mComponentResolver.getActivity(component); + // Allow to match activities of quarantined packages. + flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS; + if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName()); @@ -3862,11 +3865,13 @@ public class ComputerEngine implements Computer { Binder.restoreCallingIdentity(identity); } + var usingSharedLibraryPair = + getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId); SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getPath(), libInfo.getPackageName(), libInfo.getAllCodePaths(), libInfo.getName(), libInfo.getLongVersion(), libInfo.getType(), declaringPackage, - getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId), + usingSharedLibraryPair.first, (libInfo.getDependencies() == null ? null : new ArrayList<>(libInfo.getDependencies())), @@ -3935,13 +3940,15 @@ public class ComputerEngine implements Computer { return false; } + @Override - public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo, - @PackageManager.PackageInfoFlagsBits long flags, int callingUid, - @UserIdInt int userId) { + public Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary( + @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags, + int callingUid, @UserIdInt int userId) { List<VersionedPackage> versionedPackages = null; final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates(); final int packageCount = packageStates.size(); + List<Boolean> usesLibsOptional = null; for (int i = 0; i < packageCount; i++) { PackageStateInternal ps = packageStates.valueAt(i); if (ps == null) { @@ -3958,12 +3965,15 @@ public class ComputerEngine implements Computer { libInfo.isStatic() ? ps.getUsesStaticLibraries() : ps.getUsesSdkLibraries(); final long[] libsVersions = libInfo.isStatic() ? ps.getUsesStaticLibrariesVersions() : ps.getUsesSdkLibrariesVersionsMajor(); + final boolean[] libsOptional = libInfo.isSdk() + ? ps.getUsesSdkLibrariesOptional() : null; final int libIdx = ArrayUtils.indexOf(libs, libName); if (libIdx < 0) { continue; } if (libsVersions[libIdx] != libInfo.getLongVersion()) { + // Not expected StaticLib/SdkLib version continue; } if (shouldFilterApplication(ps, callingUid, userId)) { @@ -3972,6 +3982,9 @@ public class ComputerEngine implements Computer { if (versionedPackages == null) { versionedPackages = new ArrayList<>(); } + if (usesLibsOptional == null) { + usesLibsOptional = new ArrayList<>(); + } // If the dependent is a static shared lib, use the public package name String dependentPackageName = ps.getPackageName(); if (ps.getPkg() != null && ps.getPkg().isStaticSharedLibrary()) { @@ -3979,6 +3992,7 @@ public class ComputerEngine implements Computer { } versionedPackages.add(new VersionedPackage(dependentPackageName, ps.getVersionCode())); + usesLibsOptional.add(libsOptional != null && libsOptional[libIdx]); } else if (ps.getPkg() != null) { if (ArrayUtils.contains(ps.getPkg().getUsesLibraries(), libName) || ArrayUtils.contains(ps.getPkg().getUsesOptionalLibraries(), libName)) { @@ -3994,7 +4008,7 @@ public class ComputerEngine implements Computer { } } - return versionedPackages; + return new Pair<>(versionedPackages, usesLibsOptional); } @Nullable @@ -4053,13 +4067,14 @@ public class ComputerEngine implements Computer { Binder.restoreCallingIdentity(identity); } + var usingSharedLibraryPair = + getPackagesUsingSharedLibrary(libraryInfo, flags, callingUid, userId); SharedLibraryInfo resultLibraryInfo = new SharedLibraryInfo( libraryInfo.getPath(), libraryInfo.getPackageName(), libraryInfo.getAllCodePaths(), libraryInfo.getName(), libraryInfo.getLongVersion(), libraryInfo.getType(), libraryInfo.getDeclaringPackage(), - getPackagesUsingSharedLibrary( - libraryInfo, flags, callingUid, userId), + usingSharedLibraryPair.first, libraryInfo.getDependencies() == null ? null : new ArrayList<>(libraryInfo.getDependencies()), libraryInfo.isNative()); @@ -4625,7 +4640,7 @@ public class ComputerEngine implements Computer { @Override public List<ApplicationInfo> getInstalledApplications( @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId, - int callingUid) { + int callingUid, boolean forceAllowCrossUser) { if (getInstantAppPackageName(callingUid) != null) { return Collections.emptyList(); } @@ -4635,12 +4650,14 @@ public class ComputerEngine implements Computer { final boolean listApex = (flags & MATCH_APEX) != 0; final boolean listArchivedOnly = !listUninstalled && (flags & MATCH_ARCHIVED_PACKAGES) != 0; - enforceCrossUserPermission( - callingUid, - userId, - false /* requireFullPermission */, - false /* checkShell */, - "get installed application info"); + if (!forceAllowCrossUser) { + enforceCrossUserPermission( + callingUid, + userId, + false /* requireFullPermission */, + false /* checkShell */, + "get installed application info"); + } ArrayList<ApplicationInfo> list; final ArrayMap<String, ? extends PackageStateInternal> packageStates = @@ -4963,31 +4980,34 @@ public class ComputerEngine implements Computer { } } - private PackageUserStateInternal getUserStageOrDefaultForUser(@NonNull String packageName, - int userId) { + private PackageUserStateInternal getUserStateOrDefaultForUser(@NonNull String packageName, + int userId) throws PackageManager.NameNotFoundException { final int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */, false /* checkShell */, "when asking about packages for user " + userId); final PackageStateInternal ps = mSettings.getPackage(packageName); if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) { - throw new IllegalArgumentException("Unknown target package: " + packageName); + throw new PackageManager.NameNotFoundException(packageName); } return ps.getUserStateOrDefault(userId); } @Override - public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) { - return getUserStageOrDefaultForUser(packageName, userId).isSuspended(); + public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) + throws PackageManager.NameNotFoundException { + return getUserStateOrDefaultForUser(packageName, userId).isSuspended(); } @Override - public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) { - return getUserStageOrDefaultForUser(packageName, userId).isQuarantined(); + public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException { + return getUserStateOrDefaultForUser(packageName, userId).isQuarantined(); } @Override - public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) { - return getUserStageOrDefaultForUser(packageName, userId).isStopped(); + public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException { + return getUserStateOrDefaultForUser(packageName, userId).isStopped(); } @Override 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 80e6c833dfb2..aa7f0d3c668a 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static android.Manifest.permission.CONTROL_KEYGUARD; import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; -import static android.content.pm.Flags.sdkLibIndependence; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; @@ -40,6 +39,7 @@ import android.app.ApplicationExitInfo; import android.app.ApplicationPackageManager; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -72,8 +72,6 @@ import com.android.server.wm.ActivityTaskManagerInternal; import dalvik.system.VMRuntime; -import java.util.List; - /** * Deletes a package. Uninstall if installed, or at least deletes the base directory if it's called * from a failed installation. Fixes user state after deletion. @@ -181,16 +179,36 @@ final class DeletePackageHelper { } if (libraryInfo != null) { + boolean flagSdkLibIndependence = Flags.sdkLibIndependence(); for (int currUserId : allUsers) { if (removeUser != UserHandle.USER_ALL && removeUser != currUserId) { continue; } - List<VersionedPackage> libClientPackages = - computer.getPackagesUsingSharedLibrary(libraryInfo, - MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId); - boolean allowSdkLibIndependence = - (pkg.getSdkLibraryName() != null) && sdkLibIndependence(); - if (!ArrayUtils.isEmpty(libClientPackages) && !allowSdkLibIndependence) { + var libClientPackagesPair = computer.getPackagesUsingSharedLibrary( + libraryInfo, MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId); + var libClientPackages = libClientPackagesPair.first; + var libClientOptional = libClientPackagesPair.second; + // We by default don't allow removing a package if the host lib is still be + // used by other client packages + boolean allowLibIndependence = false; + // Only when the sdkLibIndependence flag is enabled we will respect the + // "optional" attr in uses-sdk-library. Only allow to remove sdk-lib host + // package if no required clients depend on it + if ((pkg.getSdkLibraryName() != null) + && !ArrayUtils.isEmpty(libClientPackages) + && !ArrayUtils.isEmpty(libClientOptional) + && (libClientPackages.size() == libClientOptional.size()) + && flagSdkLibIndependence) { + allowLibIndependence = true; + for (int i = 0; i < libClientPackages.size(); i++) { + boolean usesSdkLibOptional = libClientOptional.get(i); + if (!usesSdkLibOptional) { + allowLibIndependence = false; + break; + } + } + } + if (!ArrayUtils.isEmpty(libClientPackages) && !allowLibIndependence) { Slog.w(TAG, "Not removing package " + pkg.getManifestPackageName() + " hosting lib " + libraryInfo.getName() + " version " + libraryInfo.getLongVersion() + " used by " + libClientPackages @@ -352,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); @@ -392,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; @@ -442,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) { @@ -485,7 +509,11 @@ final class DeletePackageHelper { } } if (clearPackageStateAndReturn) { - mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, outInfo, flags); + mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, flags); + // Legacy behavior to report appId as UID here. + // The final broadcasts will contain a per-user UID. + outInfo.mUid = ps.getAppId(); + outInfo.mIsAppIdRemoved = true; mPm.scheduleWritePackageRestrictions(user); return; } @@ -511,12 +539,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); @@ -524,24 +548,21 @@ 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()); - } + // Since the package is being deleted in all users, report appId as the uid + 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 @@ -549,7 +570,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())); @@ -621,7 +642,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 @@ -644,10 +665,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/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java index 9a0306b77c41..e3bbd2d8d17e 100644 --- a/services/core/java/com/android/server/pm/IPackageManagerBase.java +++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java @@ -476,7 +476,8 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { @PackageManager.ApplicationInfoFlagsBits long flags, int userId) { final int callingUid = Binder.getCallingUid(); return new ParceledListSlice<>( - snapshot().getInstalledApplications(flags, userId, callingUid)); + snapshot().getInstalledApplications(flags, userId, callingUid, + /* forceAllowCrossUser= */ false)); } @Override @@ -950,20 +951,32 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { @Deprecated public final boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId) { - return snapshot().isPackageSuspendedForUser(packageName, userId); + try { + return snapshot().isPackageSuspendedForUser(packageName, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown target package: " + packageName); + } } @Override @Deprecated public final boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) { - return snapshot().isPackageQuarantinedForUser(packageName, userId); + try { + return snapshot().isPackageQuarantinedForUser(packageName, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown target package: " + packageName); + } } @Override public final boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) { - return snapshot().isPackageStoppedForUser(packageName, userId); + try { + return snapshot().isPackageStoppedForUser(packageName, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown target package: " + packageName); + } } @Override diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java index 5c4447eb99a4..3b9f9c804e27 100644 --- a/services/core/java/com/android/server/pm/InitAppsHelper.java +++ b/services/core/java/com/android/server/pm/InitAppsHelper.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; +import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX; import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME; import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME; import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX; @@ -30,7 +31,6 @@ import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX; import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN; import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS; import static com.android.server.pm.PackageManagerService.TAG; -import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX; import android.annotation.NonNull; import android.annotation.Nullable; @@ -46,12 +46,12 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedArrayMap; import java.io.File; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 83a6f10f0e2a..0fa7aa5473b2 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -154,12 +154,16 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.F2fsUtils; +import com.android.internal.pm.parsing.PackageParserException; +import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.component.ComponentMutateUtils; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.security.VerityUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -181,8 +185,6 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedLibraryWrapper; -import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.rollback.RollbackManagerInternal; import com.android.server.security.FileIntegrityService; import com.android.server.utils.WatchedArrayMap; @@ -1165,7 +1167,7 @@ final class InstallPackageHelper { parseFlags); archivedPackage = request.getPackageLite().getArchivedPackage(); } - } catch (PackageManagerException e) { + } catch (PackageManagerException | PackageParserException e) { throw new PrepareFailure("Failed parse during installPackageLI", e); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -2450,9 +2452,9 @@ final class InstallPackageHelper { // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088) mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers()); if (installRequest.isClearCodeCache()) { - mAppDataHelper.clearAppDataLeafLIF(packageName, ps.getVolumeUuid(), - UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE - | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); + mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL, + FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL + | Installer.FLAG_CLEAR_CODE_CACHE_ONLY); } if (installRequest.isInstallReplace() && pkg != null) { mDexManager.notifyPackageUpdated(packageName, @@ -2910,7 +2912,8 @@ final class InstallPackageHelper { info.mInstallerPackageName = request.getInstallerPackageName(); info.mRemovedUsers = firstUserIds; info.mBroadcastUsers = firstUserIds; - info.mRemovedAppId = request.getAppId(); + info.mUid = request.getAppId(); + info.mIsAppIdRemoved = true; info.mRemovedPackageVersionCode = request.getPkg().getLongVersionCode(); info.mRemovedForAllUsers = true; @@ -3856,7 +3859,7 @@ final class InstallPackageHelper { synchronized (mPm.mLock) { platformPackage = mPm.getPlatformPackage(); - var isSystemApp = AndroidPackageUtils.isSystem(parsedPackage); + var isSystemApp = AndroidPackageLegacyUtils.isSystem(parsedPackage); final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr( AndroidPackageUtils.getRealPackageOrNull(parsedPackage, isSystemApp)); realPkgName = ScanPackageUtils.getRealPackageName(parsedPackage, renamedPkgName, @@ -4126,7 +4129,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. @@ -4574,7 +4577,7 @@ final class InstallPackageHelper { private void assertPackageWithSharedUserIdIsPrivileged(AndroidPackage pkg) throws PackageManagerException { - if (!AndroidPackageUtils.isPrivileged(pkg) && (pkg.getSharedUserId() != null)) { + if (!AndroidPackageLegacyUtils.isPrivileged(pkg) && (pkg.getSharedUserId() != null)) { SharedUserSetting sharedUserSetting = null; try { synchronized (mPm.mLock) { @@ -4612,7 +4615,7 @@ final class InstallPackageHelper { final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0) && ScanPackageUtils.getVendorPartitionVersion() < 28; if (((scanFlags & SCAN_AS_PRIVILEGED) == 0) - && !AndroidPackageUtils.isPrivileged(pkg) + && !AndroidPackageLegacyUtils.isPrivileged(pkg) && (pkg.getSharedUserId() != null) && !skipVendorPrivilegeScan) { SharedUserSetting sharedUserSetting = null; diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 5494bd9808c8..ee780d99b6b6 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -51,12 +51,12 @@ import android.util.ExceptionUtils; import android.util.Slog; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.util.ArrayUtils; import com.android.server.art.model.DexoptResult; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.File; import java.util.ArrayList; @@ -818,7 +818,8 @@ final class InstallRequest { public void setRemovedAppId(int appId) { if (mRemovedInfo != null) { - mRemovedInfo.mRemovedAppId = appId; + mRemovedInfo.mUid = appId; + mRemovedInfo.mIsAppIdRemoved = true; } } diff --git a/services/core/java/com/android/server/pm/KnownPackages.java b/services/core/java/com/android/server/pm/KnownPackages.java index 154709a62095..83831ca61aa1 100644 --- a/services/core/java/com/android/server/pm/KnownPackages.java +++ b/services/core/java/com/android/server/pm/KnownPackages.java @@ -77,6 +77,8 @@ public final class KnownPackages { // Please note the numbers should be continuous. public static final int LAST_KNOWN_PACKAGE = PACKAGE_WEARABLE_SENSING; + static final String SYSTEM_PACKAGE_NAME = "android"; + private final DefaultAppProvider mDefaultAppProvider; private final String mRequiredInstallerPackage; private final String mRequiredUninstallerPackage; @@ -186,7 +188,7 @@ public final class KnownPackages { case PACKAGE_SETUP_WIZARD: return snapshot.filterOnlySystemPackages(mSetupWizardPackage); case PACKAGE_SYSTEM: - return new String[]{"android"}; + return new String[]{SYSTEM_PACKAGE_NAME}; case PACKAGE_VERIFIER: return snapshot.filterOnlySystemPackages(mRequiredVerifierPackages); case PACKAGE_SYSTEM_TEXT_CLASSIFIER: diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index b80c0094ffb9..ba66377beb8a 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -769,6 +769,9 @@ public class LauncherAppsService extends SystemService { @NonNull private List<LauncherActivityInfoInternal> generateLauncherActivitiesForArchivedApp( @Nullable String packageName, UserHandle user) { + if (!canAccessProfile(user.getIdentifier(), "Cannot retrieve activities")) { + return List.of(); + } List<ApplicationInfo> applicationInfoList = (packageName == null) ? getApplicationInfoListForAllArchivedApps(user) @@ -827,7 +830,7 @@ public class LauncherAppsService extends SystemService { private List<ApplicationInfo> getApplicationInfoListForAllArchivedApps(UserHandle user) { final int callingUid = injectBinderCallingUid(); List<ApplicationInfo> installedApplicationInfoList = - mPackageManagerInternal.getInstalledApplications( + mPackageManagerInternal.getInstalledApplicationsCrossUser( PackageManager.MATCH_ARCHIVED_PACKAGES, user.getIdentifier(), callingUid); @@ -845,11 +848,12 @@ public class LauncherAppsService extends SystemService { private List<ApplicationInfo> getApplicationInfoForArchivedApp( @NonNull String packageName, UserHandle user) { final int callingUid = injectBinderCallingUid(); - ApplicationInfo applicationInfo = mPackageManagerInternal.getApplicationInfo( - packageName, - PackageManager.MATCH_ARCHIVED_PACKAGES, - callingUid, - user.getIdentifier()); + ApplicationInfo applicationInfo = Binder.withCleanCallingIdentity(() -> + mPackageManagerInternal.getApplicationInfo( + packageName, + PackageManager.MATCH_ARCHIVED_PACKAGES, + callingUid, + user.getIdentifier())); if (applicationInfo == null || !applicationInfo.isArchived) { return Collections.EMPTY_LIST; } @@ -1571,6 +1575,30 @@ public class LauncherAppsService extends SystemService { } @Override + public List<String> getPreInstalledSystemPackages(UserHandle user) { + // Only system launchers, which have access to recents should have access to this API. + // TODO(b/303803157): Update access control for this API to default Launcher app. + if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) { + throw new SecurityException("Caller is not the recents app"); + } + if (!canAccessProfile(user.getIdentifier(), + "Can't access preinstalled packages for another user")) { + return null; + } + final long identity = Binder.clearCallingIdentity(); + try { + String userType = mUm.getUserInfo(user.getIdentifier()).userType; + Set<String> preInstalledPackages = mUm.getPreInstallableSystemPackages(userType); + if (preInstalledPackages == null) { + return new ArrayList<>(); + } + return List.copyOf(preInstalledPackages); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void startActivityAsUser(IApplicationThread caller, String callingPackage, String callingFeatureId, ComponentName component, Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException { diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index e3bab3f243c3..6b05edf7c25a 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -323,6 +323,7 @@ public class PackageArchiver { PackageStateInternal ps = getPackageState(packageName, snapshot, Binder.getCallingUid(), userId); verifyNotSystemApp(ps.getFlags()); + verifyInstalled(ps, userId); String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); verifyInstaller(responsibleInstallerPackage, userId); ApplicationInfo installerInfo = snapshot.getApplicationInfo( @@ -476,6 +477,14 @@ public class PackageArchiver { } } + private void verifyInstalled(PackageStateInternal ps, int userId) + throws PackageManager.NameNotFoundException { + if (!ps.getUserStateOrDefault(userId).isInstalled()) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("%s is not installed.", ps.getPackageName())); + } + } + /** * Returns true if the app is archivable. */ @@ -519,11 +528,11 @@ public class PackageArchiver { /** * Returns true if user has opted the app out of archiving through system settings. */ - // TODO(b/304256918) Switch this to a separate OP code for archiving. private boolean isAppOptedOutOfArchiving(String packageName, int uid) { return Binder.withCleanCallingIdentity(() -> - getAppOpsManager().checkOp(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, - uid, packageName) == MODE_IGNORED); + getAppOpsManager().checkOpNoThrow( + AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, packageName) + == MODE_IGNORED); } private void verifyOptOutStatus(String packageName, int uid) @@ -676,23 +685,51 @@ public class PackageArchiver { PackageStateInternal ps; try { ps = getPackageState(packageName, snapshot, callingUid, userId); - snapshot.enforceCrossUserPermission(callingUid, userId, true, false, - "getArchivedAppIcon"); - verifyArchived(ps, userId); } catch (PackageManager.NameNotFoundException e) { - throw new ParcelableException(e); + Slog.e(TAG, TextUtils.formatSimple("Package %s couldn't be found.", packageName), e); + return null; } - List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault( - userId).getArchiveState().getActivityInfos(); - if (activityInfos.size() == 0) { + ArchiveState archiveState = getAnyArchiveState(ps, userId); + if (archiveState == null || archiveState.getActivityInfos().size() == 0) { return null; } // TODO(b/298452477) Handle monochrome icons. // In the rare case the archived app defined more than two launcher activities, we choose // the first one arbitrarily. - return includeCloudOverlay(decodeIcon(activityInfos.get(0))); + return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0))); + } + + /** + * This method first checks the ArchiveState for the provided userId and then tries to fallback + * to other users if the current user is not archived. + * + * <p> This fallback behaviour is required for archived apps to fit into the multi-user world + * where APKs are shared across users. E.g. current ways of fetching icons for apps that are + * only installed on the work profile also work when executed on the personal profile if you're + * using {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}. Resource fetching from APKs is for + * the most part userId-agnostic, which we need to mimic here in order for existing methods + * like {@link PackageManager#getApplicationIcon} to continue working. + * + * @return {@link ArchiveState} for {@code userId} if present. If not present, false back to an + * arbitrary userId. If no user is archived, returns null. + */ + @Nullable + private ArchiveState getAnyArchiveState(PackageStateInternal ps, int userId) { + PackageUserStateInternal userState = ps.getUserStateOrDefault(userId); + if (isArchived(userState)) { + return userState.getArchiveState(); + } + + for (int i = 0; i < ps.getUserStates().size(); i++) { + userState = ps.getUserStates().valueAt(i); + if (isArchived(userState)) { + return userState.getArchiveState(); + } + } + + return null; } @VisibleForTesting diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 47d1df5df1c0..4adb60c34c52 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -168,6 +168,7 @@ import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.os.SomeArgs; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.security.VerityUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -181,7 +182,6 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexManager; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import libcore.io.IoUtils; import libcore.util.EmptyArray; diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index b281808e89b6..c737b45ae885 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -97,7 +97,16 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { @Deprecated public final List<ApplicationInfo> getInstalledApplications( @PackageManager.ApplicationInfoFlagsBits long flags, int userId, int callingUid) { - return snapshot().getInstalledApplications(flags, userId, callingUid); + return snapshot().getInstalledApplications(flags, userId, callingUid, + /* forceAllowCrossUser= */ false); + } + + @Override + @Deprecated + public final List<ApplicationInfo> getInstalledApplicationsCrossUser( + @PackageManager.ApplicationInfoFlagsBits long flags, int userId, int callingUid) { + return snapshot().getInstalledApplications(flags, userId, callingUid, + /* forceAllowCrossUser= */ true); } @Override @@ -753,13 +762,14 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { } @Override - public boolean isPackageQuarantined(@NonNull String packageName, - @UserIdInt int userId) { + public boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException { return snapshot().isPackageQuarantinedForUser(packageName, userId); } @Override - public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId) { + public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId) + throws PackageManager.NameNotFoundException { return snapshot().isPackageStoppedForUser(packageName, userId); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f27c462700be..56365b676618 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -185,6 +185,7 @@ import com.android.internal.pm.parsing.pkg.AndroidPackageInternal; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -238,7 +239,6 @@ import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.pm.pkg.mutate.PackageStateWrite; import com.android.server.pm.pkg.mutate.PackageUserStateWrite; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.resolution.ComponentResolver; import com.android.server.pm.resolution.ComponentResolverApi; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; @@ -2013,6 +2013,16 @@ public class PackageManagerService implements PackageSender, TestUtilityService public boolean hasFeature(String feature) { return PackageManagerService.this.hasSystemFeature(feature, 0); } + + @Override + public Set<String> getHiddenApiWhitelistedApps() { + return SystemConfig.getInstance().getHiddenApiWhitelistedApps(); + } + + @Override + public Set<String> getInstallConstraintsAllowlist() { + return SystemConfig.getInstance().getInstallConstraintsAllowlist(); + } }; // CHECKSTYLE:ON IndentationCheck @@ -3044,6 +3054,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + @NonNull int[] resolveUserIds(int userId) { return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId }; } @@ -5241,14 +5252,18 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override public String getSuspendingPackage(String packageName, int userId) { - final int callingUid = Binder.getCallingUid(); - final Computer snapshot = snapshot(); - // This will do visibility checks as well. - if (!snapshot.isPackageSuspendedForUser(packageName, userId)) { + try { + final int callingUid = Binder.getCallingUid(); + final Computer snapshot = snapshot(); + // This will do visibility checks as well. + if (!snapshot.isPackageSuspendedForUser(packageName, userId)) { + return null; + } + return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId, + callingUid); + } catch (PackageManager.NameNotFoundException e) { return null; } - return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId, - callingUid); } @Override @@ -7113,9 +7128,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (info == null) { continue; } - final List<VersionedPackage> dependents = - computer.getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID, - userId); + var usingSharedLibraryPair = computer.getPackagesUsingSharedLibrary(info, 0, + Process.SYSTEM_UID, userId); + final List<VersionedPackage> dependents = usingSharedLibraryPair.first; if (dependents == null) { continue; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 215e9528a35e..322557b9e496 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -2872,16 +2872,16 @@ class PackageManagerShellCommand extends ShellCommand { UserHandle.USER_NULL, "runGrantRevokePermission")); List<PackageInfo> packageInfos; + PackageManager pm = mContext.createContextAsUser(translatedUser, 0).getPackageManager(); if (pkg == null) { - packageInfos = mContext.getPackageManager().getInstalledPackages( - PackageManager.GET_PERMISSIONS); + packageInfos = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS); } else { try { - packageInfos = Collections.singletonList( - mContext.getPackageManager().getPackageInfo(pkg, - PackageManager.GET_PERMISSIONS)); + packageInfos = Collections.singletonList(pm.getPackageInfo(pkg, + PackageManager.GET_PERMISSIONS)); } catch (NameNotFoundException e) { getErrPrintWriter().println("Error: package not found"); + getOutPrintWriter().println("Failure [package not found]"); return 1; } } diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java index 7ee1772adead..881b0b398f9a 100644 --- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java +++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java @@ -25,7 +25,7 @@ final class PackageRemovedInfo { String mRemovedPackage; String mInstallerPackageName; int mUid = -1; - int mRemovedAppId = -1; + boolean mIsAppIdRemoved = false; int[] mOrigUsers; int[] mRemovedUsers = null; int[] mBroadcastUsers = null; diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 174df44c4263..7d0a1f6afe1d 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -123,11 +123,15 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @Nullable private Map<String, Set<String>> mimeGroups; + // TODO(b/314036181): encapsulate all these fields for usesSdk, instead of having three + // separate arrays. @Nullable private String[] usesSdkLibraries; @Nullable private long[] usesSdkLibrariesVersionsMajor; + @Nullable + private boolean[] usesSdkLibrariesOptional; @Nullable private String[] usesStaticLibraries; @@ -701,6 +705,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal usesSdkLibrariesVersionsMajor = other.usesSdkLibrariesVersionsMajor != null ? Arrays.copyOf(other.usesSdkLibrariesVersionsMajor, other.usesSdkLibrariesVersionsMajor.length) : null; + usesSdkLibrariesOptional = other.usesSdkLibrariesOptional != null + ? Arrays.copyOf(other.usesSdkLibrariesOptional, + other.usesSdkLibrariesOptional.length) : null; usesStaticLibraries = other.usesStaticLibraries != null ? Arrays.copyOf(other.usesStaticLibraries, @@ -1344,6 +1351,12 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @NonNull @Override + public boolean[] getUsesSdkLibrariesOptional() { + return usesSdkLibrariesOptional == null ? EmptyArray.BOOLEAN : usesSdkLibrariesOptional; + } + + @NonNull + @Override public String[] getUsesStaticLibraries() { return usesStaticLibraries == null ? EmptyArray.STRING : usesStaticLibraries; } @@ -1444,6 +1457,12 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } + public PackageSetting setUsesSdkLibrariesOptional(boolean[] usesSdkLibrariesOptional) { + this.usesSdkLibrariesOptional = usesSdkLibrariesOptional; + onChanged(); + return this; + } + public PackageSetting setUsesStaticLibraries(String[] usesStaticLibraries) { this.usesStaticLibraries = usesStaticLibraries; onChanged(); diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index bb0017c80d6d..b6de0e5c030f 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -20,19 +20,23 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIB import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY; +import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX; import static com.android.server.pm.PackageManagerService.SCAN_BOOTING; import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP; +import static com.android.server.pm.PackageManagerService.TAG; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; +import android.os.Build; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedLongSparseArray; import java.util.ArrayList; @@ -49,6 +53,8 @@ import java.util.Map; * as install) led to the request. */ final class ReconcilePackageUtils { + private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true; + public static List<ReconciledPackage> reconcilePackages( List<InstallRequest> installRequests, Map<String, AndroidPackage> allPackages, @@ -90,6 +96,8 @@ final class ReconcilePackageUtils { } } + final AndroidPackage systemPackage = allPackages.get(KnownPackages.SYSTEM_PACKAGE_NAME); + for (InstallRequest installRequest : installRequests) { final String installPackageName = installRequest.getParsedPackage().getPackageName(); final List<SharedLibraryInfo> allowedSharedLibInfos = @@ -133,6 +141,9 @@ final class ReconcilePackageUtils { if (parsedPackage != null) { signingDetails = parsedPackage.getSigningDetails(); } + final boolean isSystemPackage = + ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0); + final boolean isApex = (scanFlags & SCAN_AS_APEX) != 0; SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr( signatureCheckPs); if (ksms.shouldCheckUpgradeKeySetLocked( @@ -141,7 +152,7 @@ final class ReconcilePackageUtils { // We just determined the app is signed correctly, so bring // over the latest parsed certs. } else { - if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { + if (!isSystemPackage) { throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + parsedPackage.getPackageName() + " upgrade keys do not match the previously installed" @@ -168,9 +179,23 @@ final class ReconcilePackageUtils { removeAppKeySetData = true; } + if (!isSystemPackage && !isApex && signingDetails != null + && systemPackage != null && systemPackage.getSigningDetails() != null + && systemPackage.getSigningDetails().checkCapability( + signingDetails, + SigningDetails.CertCapabilities.PERMISSION)) { + Slog.d(TAG, "Non-preload app associated with system signature: " + + signatureCheckPs.getPackageName()); + if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) { + throw new ReconcileFailure( + INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + "Non-preload app associated with system signature: " + + signatureCheckPs.getPackageName()); + } + } + // if this is is a sharedUser, check to see if the new package is signed by a - // newer - // signing certificate than the existing one, and if so, copy over the new + // newer signing certificate than the existing one, and if so, copy over the new // details if (sharedUserSetting != null) { // Attempt to merge the existing lineage for the shared SigningDetails with @@ -203,7 +228,7 @@ final class ReconcilePackageUtils { } } } catch (PackageManagerException e) { - if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { + if (!isSystemPackage) { throw new ReconcileFailure(e); } signingDetails = parsedPackage.getSigningDetails(); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 52b31319cc19..fefab3ba15b9 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -44,12 +44,12 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.util.ArrayUtils; import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.parsing.PackageCacher; -import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; @@ -167,7 +167,7 @@ final class RemovePackageHelper { if (removedPackage != null) { // TODO: Use PackageState for isSystem cleanPackageDataStructuresLILPw(removedPackage, - AndroidPackageUtils.isSystem(removedPackage), chatty); + AndroidPackageLegacyUtils.isSystem(removedPackage), chatty); } } } @@ -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,7 @@ final class RemovePackageHelper { mPm.mAppsFilter.removePackage(snapshot, snapshot.getPackageStateInternal(packageName)); removedAppId = mPm.mSettings.removePackageLPw(packageName); - if (outInfo != null) { - outInfo.mRemovedAppId = removedAppId; - } + outInfo.mIsAppIdRemoved = true; 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 +375,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 +396,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/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 53b84e66840b..5c6d61e3eaf9 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -74,11 +74,13 @@ import android.util.jar.StrictJarFile; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.component.ComponentMutateUtils; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.parsing.PackageInfoUtils; @@ -86,8 +88,6 @@ import com.android.server.pm.parsing.library.PackageBackwardCompatibility; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateUtils; -import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedArraySet; import dalvik.system.VMRuntime; @@ -218,7 +218,8 @@ final class ScanPackageUtils { parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user, true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp, UserManagerService.getInstance(), usesSdkLibraries, - parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries, + parsedPackage.getUsesSdkLibrariesVersionsMajor(), + parsedPackage.getUsesSdkLibrariesOptional(), usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash()); @@ -240,6 +241,7 @@ final class ScanPackageUtils { PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting), UserManagerService.getInstance(), usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(), + parsedPackage.getUsesSdkLibrariesOptional(), usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash()); diff --git a/services/core/java/com/android/server/pm/ScanRequest.java b/services/core/java/com/android/server/pm/ScanRequest.java index 37cf30bd63fe..41e2a3f3d318 100644 --- a/services/core/java/com/android/server/pm/ScanRequest.java +++ b/services/core/java/com/android/server/pm/ScanRequest.java @@ -22,8 +22,8 @@ import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; /** A package to be scanned */ @VisibleForTesting diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 2cbf714792f7..cfbaae3d0f30 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -89,6 +89,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedPermission; @@ -339,6 +340,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_DISTRACTION_FLAGS = "distraction_flags"; private static final String ATTR_SUSPENDED = "suspended"; private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package"; + + private static final String ATTR_OPTIONAL = "optional"; /** * @deprecated Legacy attribute, kept only for upgrading from P builds. */ @@ -942,6 +945,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ret.setLongVersionCode(p.getVersionCode()); ret.setUsesSdkLibraries(p.getUsesSdkLibraries()); ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor()); + ret.setUsesSdkLibrariesOptional(p.getUsesSdkLibrariesOptional()); ret.setUsesStaticLibraries(p.getUsesStaticLibraries()); ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions()); ret.setMimeGroups(p.getMimeGroups()); @@ -1061,9 +1065,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile UserHandle installUser, boolean allowInstall, boolean instantApp, boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager, String[] usesSdkLibraries, long[] usesSdkLibrariesVersions, - String[] usesStaticLibraries, long[] usesStaticLibrariesVersions, - Set<String> mimeGroupNames, @NonNull UUID domainSetId, - int targetSdkVersion, byte[] restrictUpdatedHash) { + boolean[] usesSdkLibrariesOptional, String[] usesStaticLibraries, + long[] usesStaticLibrariesVersions, Set<String> mimeGroupNames, + @NonNull UUID domainSetId, int targetSdkVersion, byte[] restrictUpdatedHash) { final PackageSetting pkgSetting; if (originalPkg != null) { if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " @@ -1079,6 +1083,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .setLongVersionCode(versionCode) .setUsesSdkLibraries(usesSdkLibraries) .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions) + .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional) .setUsesStaticLibraries(usesStaticLibraries) .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions) // Update new package state. @@ -1096,6 +1101,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pkgPrivateFlags, domainSetId) .setUsesSdkLibraries(usesSdkLibraries) .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions) + .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional) .setUsesStaticLibraries(usesStaticLibraries) .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions) .setLegacyNativeLibraryPath(legacyNativeLibraryPath) @@ -1218,6 +1224,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags, int pkgPrivateFlags, @NonNull UserManagerService userManager, @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions, + @Nullable boolean[] usesSdkLibrariesOptional, @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions, @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, int targetSdkVersion, byte[] restrictUpdatedHash) @@ -1277,12 +1284,17 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .setRestrictUpdateHash(restrictUpdatedHash); // Update SDK library dependencies if needed. if (usesSdkLibraries != null && usesSdkLibrariesVersions != null - && usesSdkLibraries.length == usesSdkLibrariesVersions.length) { + && usesSdkLibrariesOptional != null + && usesSdkLibraries.length == usesSdkLibrariesVersions.length + && usesSdkLibraries.length == usesSdkLibrariesOptional.length) { pkgSetting.setUsesSdkLibraries(usesSdkLibraries) - .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions); + .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions) + .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional); } else { pkgSetting.setUsesSdkLibraries(null) - .setUsesSdkLibrariesVersionsMajor(null); + .setUsesSdkLibrariesVersionsMajor(null) + .setUsesSdkLibrariesOptional(null); + } // Update static shared library dependencies if needed. @@ -2537,12 +2549,15 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile throws IOException, XmlPullParserException { String libName = parser.getAttributeValue(null, ATTR_NAME); long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1); + boolean optional = parser.getAttributeBoolean(null, ATTR_OPTIONAL, true); if (libName != null && libVersion >= 0) { outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class, outPs.getUsesSdkLibraries(), libName)); outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong( outPs.getUsesSdkLibrariesVersionsMajor(), libVersion)); + outPs.setUsesSdkLibrariesOptional(PackageImpl.appendBoolean( + outPs.getUsesSdkLibrariesOptional(), optional)); } XmlUtils.skipCurrentTag(parser); @@ -2564,7 +2579,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } void writeUsesSdkLibLPw(TypedXmlSerializer serializer, String[] usesSdkLibraries, - long[] usesSdkLibraryVersions) throws IOException { + long[] usesSdkLibraryVersions, boolean[] usesSdkLibrariesOptional) throws IOException { if (ArrayUtils.isEmpty(usesSdkLibraries) || ArrayUtils.isEmpty(usesSdkLibraryVersions) || usesSdkLibraries.length != usesSdkLibraryVersions.length) { return; @@ -2573,9 +2588,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile for (int i = 0; i < libCount; i++) { final String libName = usesSdkLibraries[i]; final long libVersion = usesSdkLibraryVersions[i]; + boolean libOptional = usesSdkLibrariesOptional[i]; serializer.startTag(null, TAG_USES_SDK_LIB); serializer.attribute(null, ATTR_NAME, libName); serializer.attributeLong(null, ATTR_VERSION, libVersion); + serializer.attributeBoolean(null, ATTR_OPTIONAL, libOptional); serializer.endTag(null, TAG_USES_SDK_LIB); } } @@ -3106,7 +3123,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(), - pkg.getUsesSdkLibrariesVersionsMajor()); + pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional()); + writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(), pkg.getUsesStaticLibrariesVersions()); @@ -3206,7 +3224,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(), - pkg.getUsesSdkLibrariesVersionsMajor()); + pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional()); writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(), pkg.getUsesStaticLibrariesVersions()); @@ -5091,12 +5109,14 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile List<String> usesSdkLibraries = pkg.getUsesSdkLibraries(); long[] usesSdkLibrariesVersionsMajor = pkg.getUsesSdkLibrariesVersionsMajor(); + boolean[] usesSdkLibrariesOptional = pkg.getUsesSdkLibrariesOptional(); if (usesSdkLibraries.size() > 0) { pw.print(prefix); pw.println(" usesSdkLibraries:"); for (int i = 0, size = usesSdkLibraries.size(); i < size; ++i) { pw.print(prefix); pw.print(" "); pw.print(usesSdkLibraries.get(i)); pw.print(" version:"); - pw.println(usesSdkLibrariesVersionsMajor[i]); + pw.println(usesSdkLibrariesVersionsMajor[i]); pw.print(" optional:"); + pw.println(usesSdkLibrariesOptional[i]); } } diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index 9384c13e583b..ec8af2ecd070 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -16,7 +16,6 @@ package com.android.server.pm; -import static android.content.pm.Flags.sdkLibIndependence; import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST; @@ -28,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; @@ -82,6 +82,8 @@ import java.util.function.BiConsumer; public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable { private static final boolean DEBUG_SHARED_LIBRARIES = false; + private static final String LIBRARY_TYPE_SDK = "sdk"; + /** * Apps targeting Android S and above need to declare dependencies to the public native * shared libraries that are defined by the device maker using {@code uses-native-library} tag @@ -729,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); } } @@ -798,8 +800,9 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable // Remove the shared library overlays from its dependent packages. for (int currentUserId : mPm.mUserManager.getUserIds()) { - final List<VersionedPackage> dependents = snapshot.getPackagesUsingSharedLibrary( - libraryInfo, 0, Process.SYSTEM_UID, currentUserId); + var usingSharedLibraryPair = snapshot.getPackagesUsingSharedLibrary(libraryInfo, 0, + Process.SYSTEM_UID, currentUserId); + final List<VersionedPackage> dependents = usingSharedLibraryPair.first; if (dependents == null) { continue; } @@ -921,42 +924,43 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable // duplicates. ArrayList<SharedLibraryInfo> usesLibraryInfos = null; if (!pkg.getUsesLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null, pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null, availablePackages, newLibraries); } if (!pkg.getUsesStaticLibraries().isEmpty()) { usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(), pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(), - pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(), - usesLibraryInfos, availablePackages, newLibraries); + null, pkg.getPackageName(), "static shared", true, + pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } if (!pkg.getUsesOptionalLibraries().isEmpty()) { usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null, - pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(), + null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES, pkg.getPackageName(), pkg.getTargetSdkVersion())) { if (!pkg.getUsesNativeLibraries().isEmpty()) { usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, - null, pkg.getPackageName(), "native shared", true, + null, null, pkg.getPackageName(), "native shared", true, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) { usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(), - null, null, pkg.getPackageName(), "native shared", false, + null, null, null, pkg.getPackageName(), "native shared", false, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } } if (!pkg.getUsesSdkLibraries().isEmpty()) { // Allow installation even if sdk-library dependency doesn't exist - boolean required = !sdkLibIndependence(); + boolean required = !Flags.sdkLibIndependence(); usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(), pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(), - pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(), + pkg.getUsesSdkLibrariesOptional(), + pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } return usesLibraryInfos; @@ -965,6 +969,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos( @NonNull List<String> requestedLibraries, @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests, + @Nullable boolean[] libsOptional, @NonNull String packageName, @NonNull String libraryType, boolean required, int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries, @NonNull final Map<String, AndroidPackage> availablePackages, @@ -981,7 +986,10 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable libName, libVersion, mSharedLibraries, newLibraries); } if (libraryInfo == null) { - if (required) { + // Only allow app be installed if the app specifies the sdk-library dependency is + // optional + if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null + && !libsOptional[i]))) { throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, "Package " + packageName + " requires unavailable " + libraryType + " library " + libName + "; failing!"); diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index dddc6b0fbb7a..5c0a15a28285 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -25,14 +25,14 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; +import com.android.internal.pm.pkg.component.ComponentMutateUtils; import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProcessImpl; import com.android.internal.util.ArrayUtils; import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; import com.android.server.utils.WatchedArraySet; diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 70aa19ae9cef..7d87d1b27da0 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -47,11 +47,11 @@ import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.policy.AttributeCache; import com.android.internal.util.IndentingPrintWriter; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.File; import java.io.PrintWriter; @@ -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/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index 71f6c0d507d4..c2a960a95394 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -33,7 +33,6 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; -import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; @@ -419,11 +418,24 @@ public final class SuspendPackageHelper { } String suspendingPackage = null; + String suspendedBySystem = null; + String qasPackage = null; for (int i = 0; i < userState.getSuspendParams().size(); i++) { suspendingPackage = userState.getSuspendParams().keyAt(i); + var suspendParams = userState.getSuspendParams().valueAt(i); if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) { - return suspendingPackage; + suspendedBySystem = suspendingPackage; } + if (suspendParams.isQuarantined() && qasPackage == null) { + qasPackage = suspendingPackage; + } + } + // Precedence: quarantined, then system, then suspending. + if (qasPackage != null) { + return qasPackage; + } + if (suspendedBySystem != null) { + return suspendedBySystem; } return suspendingPackage; } @@ -504,10 +516,6 @@ public final class SuspendPackageHelper { final String requiredPermissionControllerPackage = getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER, userId); - final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class); - final boolean isSystemExemptFlagEnabled = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, - SYSTEM_EXEMPT_FROM_SUSPENSION, /* defaultValue= */ true); for (int i = 0; i < packageNames.length; i++) { canSuspend[i] = false; final String packageName = packageNames[i]; @@ -581,9 +589,7 @@ public final class SuspendPackageHelper { + pkg.getStaticSharedLibraryName()); continue; } - if (isSystemExemptFlagEnabled && appOpsManager.checkOpNoThrow( - AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName) - == AppOpsManager.MODE_ALLOWED) { + if (exemptFromSuspensionByAppOp(uid, packageName)) { Slog.w(TAG, "Cannot suspend package \"" + packageName + "\": has OP_SYSTEM_EXEMPT_FROM_SUSPENSION set"); continue; @@ -601,6 +607,13 @@ public final class SuspendPackageHelper { return canSuspend; } + private boolean exemptFromSuspensionByAppOp(int uid, String packageName) { + final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class); + return appOpsManager.checkOpNoThrow( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName) + == AppOpsManager.MODE_ALLOWED; + } + /** * Suspends packages on behalf of an admin. * diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index b53a21c9aa1c..c48eccf2aac5 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -532,9 +532,10 @@ public class UserManagerService extends IUserManager.Stub { } final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class); final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL); + final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); // Call setQuietModeEnabled on bg thread to avoid ANR BackgroundThread.getHandler().post(() -> - setQuietModeEnabled(userId, false, target, /* callingPackage */ null)); + setQuietModeEnabled(userId, false, target, callingPackage)); } }; @@ -1410,7 +1411,7 @@ public class UserManagerService extends IUserManager.Stub { if (onlyIfCredentialNotRequired) { return false; } - showConfirmCredentialToDisableQuietMode(userId, target); + showConfirmCredentialToDisableQuietMode(userId, target, callingPackage); return false; } } @@ -1434,7 +1435,7 @@ public class UserManagerService extends IUserManager.Stub { if (onlyIfCredentialNotRequired) { return false; } - showConfirmCredentialToDisableQuietMode(userId, target); + showConfirmCredentialToDisableQuietMode(userId, target, callingPackage); return false; } setQuietModeEnabled(userId, false /* enableQuietMode */, target, callingPackage); @@ -1519,7 +1520,7 @@ public class UserManagerService extends IUserManager.Stub { try { if (enableQuietMode) { - ActivityManager.getService().stopUser(userId, /* force= */ true, null); + stopUserForQuietMode(userId); LocalServices.getService(ActivityManagerInternal.class) .killForegroundAppsForUser(userId); } else { @@ -1547,6 +1548,18 @@ public class UserManagerService extends IUserManager.Stub { } } + private void stopUserForQuietMode(int userId) throws RemoteException { + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) { + // Allow delayed locking since some profile types want to be able to unlock again via + // biometrics. + ActivityManager.getService() + .stopUserWithDelayedLocking(userId, /* force= */ true, null); + return; + } + ActivityManager.getService().stopUser(userId, /* force= */ true, null); + } + private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode, @Nullable String callingPackage) { Slogf.i(LOG_TAG, @@ -1592,7 +1605,7 @@ public class UserManagerService extends IUserManager.Stub { * Show confirm credential screen to unlock user in order to turn off quiet mode. */ private void showConfirmCredentialToDisableQuietMode( - @UserIdInt int userId, @Nullable IntentSender target) { + @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) { if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) { // TODO (b/308121702) It may be brittle to rely on user states to check profile state int state; @@ -1623,6 +1636,7 @@ public class UserManagerService extends IUserManager.Stub { } callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId); callBackIntent.setPackage(mContext.getPackageName()); + callBackIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackage); callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final PendingIntent pendingIntent = PendingIntent.getBroadcast( mContext, diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 7f013b8df444..14db70e5f72e 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -306,11 +306,11 @@ public final class UserTypeFactory { .setDarkThemeBadgeColors( R.color.white) .setDefaultRestrictions(getDefaultProfileRestrictions()) - .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings()) .setDefaultUserProperties(new UserProperties.Builder() .setStartWithParent(true) .setCredentialShareableWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(true) + .setAllowStoppingUserWithDelayedLocking(true) .setMediaSharedWithParent(false) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java index 459e2cf7f5c0..79c9c8ec8b0b 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java +++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java @@ -28,9 +28,9 @@ import android.system.StructStat; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.ApexManager; -import com.android.server.pm.parsing.pkg.PackageImpl; import libcore.io.IoUtils; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d64201893400..fa54f0ba18cd 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -52,6 +52,9 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils; +import com.android.internal.pm.parsing.pkg.PackageImpl; +import com.android.internal.pm.pkg.component.ComponentParseUtils; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedAttribution; import com.android.internal.pm.pkg.component.ParsedComponent; @@ -63,11 +66,12 @@ import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.PackageArchiver; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUnserialized; @@ -75,9 +79,6 @@ import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SELinuxUtil; -import com.android.server.pm.pkg.component.ComponentParseUtils; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; import java.io.File; import java.util.ArrayList; @@ -266,17 +267,20 @@ public class PackageInfoUtils { if ((flags & PackageManager.GET_ACTIVITIES) != 0) { final int N = pkg.getActivities().size(); if (N > 0) { + // Allow to match activities of quarantined packages. + long aflags = flags | PackageManager.MATCH_QUARANTINED_COMPONENTS; + int num = 0; final ActivityInfo[] res = new ActivityInfo[N]; for (int i = 0; i < N; i++) { final ParsedActivity a = pkg.getActivities().get(i); - if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a, - flags)) { + if (PackageUserStateUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), + a.isEnabled(), a.isDirectBootAware(), a.getName(), aflags)) { if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals( a.getName())) { continue; } - res[num++] = generateActivityInfo(pkg, a, flags, state, + res[num++] = generateActivityInfo(pkg, a, aflags, state, applicationInfo, userId, pkgSetting); } } @@ -290,8 +294,8 @@ public class PackageInfoUtils { final ActivityInfo[] res = new ActivityInfo[size]; for (int i = 0; i < size; i++) { final ParsedActivity a = pkg.getReceivers().get(i); - if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a, - flags)) { + if (PackageUserStateUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), + a.isEnabled(), a.isDirectBootAware(), a.getName(), flags)) { res[num++] = generateActivityInfo(pkg, a, flags, state, applicationInfo, userId, pkgSetting); } @@ -306,8 +310,8 @@ public class PackageInfoUtils { final ServiceInfo[] res = new ServiceInfo[size]; for (int i = 0; i < size; i++) { final ParsedService s = pkg.getServices().get(i); - if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), s, - flags)) { + if (PackageUserStateUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), + s.isEnabled(), s.isDirectBootAware(), s.getName(), flags)) { res[num++] = generateServiceInfo(pkg, s, flags, state, applicationInfo, userId, pkgSetting); } @@ -323,8 +327,8 @@ public class PackageInfoUtils { for (int i = 0; i < size; i++) { final ParsedProvider pr = pkg.getProviders() .get(i); - if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), pr, - flags)) { + if (PackageUserStateUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), + pr.isEnabled(), pr.isDirectBootAware(), pr.getName(), flags)) { res[num++] = generateProviderInfo(pkg, pr, flags, state, applicationInfo, userId, pkgSetting); } @@ -920,7 +924,7 @@ public class PackageInfoUtils { | flag(pkg.isExtraLargeScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) | flag(pkg.isResizeable(), ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) | flag(pkg.isAnyDensity(), ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) - | flag(AndroidPackageUtils.isSystem(pkg), ApplicationInfo.FLAG_SYSTEM) + | flag(AndroidPackageLegacyUtils.isSystem(pkg), ApplicationInfo.FLAG_SYSTEM) | flag(pkg.isFactoryTest(), ApplicationInfo.FLAG_FACTORY_TEST); return appInfoFlags(pkgWithoutStateFlags, pkgSetting); @@ -961,12 +965,12 @@ public class PackageInfoUtils { | flag(pkg.isSaveStateDisallowed(), ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) | flag(pkg.isResizeableActivityViaSdkVersion(), ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION) | flag(pkg.isAllowNativeHeapPointerTagging(), ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING) - | flag(AndroidPackageUtils.isSystemExt(pkg), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) - | flag(AndroidPackageUtils.isPrivileged(pkg), ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) - | flag(AndroidPackageUtils.isOem(pkg), ApplicationInfo.PRIVATE_FLAG_OEM) - | flag(AndroidPackageUtils.isVendor(pkg), ApplicationInfo.PRIVATE_FLAG_VENDOR) - | flag(AndroidPackageUtils.isProduct(pkg), ApplicationInfo.PRIVATE_FLAG_PRODUCT) - | flag(AndroidPackageUtils.isOdm(pkg), ApplicationInfo.PRIVATE_FLAG_ODM) + | flag(AndroidPackageLegacyUtils.isSystemExt(pkg), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) + | flag(AndroidPackageLegacyUtils.isPrivileged(pkg), ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) + | flag(AndroidPackageLegacyUtils.isOem(pkg), ApplicationInfo.PRIVATE_FLAG_OEM) + | flag(AndroidPackageLegacyUtils.isVendor(pkg), ApplicationInfo.PRIVATE_FLAG_VENDOR) + | flag(AndroidPackageLegacyUtils.isProduct(pkg), ApplicationInfo.PRIVATE_FLAG_PRODUCT) + | flag(AndroidPackageLegacyUtils.isOdm(pkg), ApplicationInfo.PRIVATE_FLAG_ODM) | flag(pkg.isSignedWithPlatformKey(), ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY); Boolean resizeableActivity = pkg.getResizeableActivity(); diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java index 1c751e07bbbe..b6a08a5a546f 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java +++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java @@ -35,17 +35,19 @@ import android.util.DisplayMetrics; import android.util.Slog; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.internal.pm.pkg.parsing.ParsingUtils; import com.android.internal.util.ArrayUtils; +import com.android.server.SystemConfig; import com.android.server.pm.PackageManagerException; import com.android.server.pm.PackageManagerService; -import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; -import com.android.server.pm.pkg.parsing.ParsingUtils; import java.io.File; import java.util.List; +import java.util.Set; /** * The v2 of package parsing for use when parsing is initiated in the server and must @@ -88,6 +90,16 @@ public class PackageParser2 implements AutoCloseable { // behavior. return false; } + + @Override + public Set<String> getHiddenApiWhitelistedApps() { + return SystemConfig.getInstance().getHiddenApiWhitelistedApps(); + } + + @Override + public Set<String> getInstallConstraintsAllowlist() { + return SystemConfig.getInstance().getInstallConstraintsAllowlist(); + } }); } @@ -221,7 +233,7 @@ public class PackageParser2 implements AutoCloseable { @NonNull String baseCodePath, @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) { return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray, - isCoreApp); + isCoreApp, Callback.this); } /** diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index 61be6e1036f2..1b7c7ad94dc9 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -29,7 +29,9 @@ import android.content.pm.parsing.result.ParseTypeImpl; import android.os.incremental.IncrementalManager; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.parsing.PackageParserException; import com.android.internal.pm.parsing.pkg.AndroidPackageHidden; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.pm.pkg.component.ParsedProvider; @@ -37,7 +39,6 @@ import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.pm.pkg.parsing.ParsingPackageHidden; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; -import com.android.server.pm.PackageManagerException; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; @@ -134,10 +135,10 @@ public class AndroidPackageUtils { /** * Validate the dex metadata files installed for the given package. * - * @throws PackageManagerException in case of errors. + * @throws PackageParserException in case of errors. */ public static void validatePackageDexMetadata(AndroidPackage pkg) - throws PackageManagerException { + throws PackageParserException { Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values(); String packageName = pkg.getPackageName(); long versionCode = pkg.getLongVersionCode(); @@ -146,7 +147,7 @@ public class AndroidPackageUtils { final ParseResult result = DexMetadataHelper.validateDexMetadataFile( input.reset(), dexMetadata, packageName, versionCode); if (result.isError()) { - throw new PackageManagerException( + throw new PackageParserException( result.getErrorCode(), result.getErrorMessage(), result.getException()); } } @@ -314,60 +315,4 @@ public class AndroidPackageUtils { info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode(); info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor(); } - - /** - * @deprecated Use {@link PackageState#isSystem} - */ - @Deprecated - public static boolean isSystem(@NonNull AndroidPackage pkg) { - return ((AndroidPackageHidden) pkg).isSystem(); - } - - /** - * @deprecated Use {@link PackageState#isSystemExt} - */ - @Deprecated - public static boolean isSystemExt(@NonNull AndroidPackage pkg) { - return ((AndroidPackageHidden) pkg).isSystemExt(); - } - - /** - * @deprecated Use {@link PackageState#isPrivileged} - */ - @Deprecated - public static boolean isPrivileged(@NonNull AndroidPackage pkg) { - return ((AndroidPackageHidden) pkg).isPrivileged(); - } - - /** - * @deprecated Use {@link PackageState#isOem} - */ - @Deprecated - public static boolean isOem(@NonNull AndroidPackage pkg) { - return ((AndroidPackageHidden) pkg).isOem(); - } - - /** - * @deprecated Use {@link PackageState#isVendor} - */ - @Deprecated - public static boolean isVendor(@NonNull AndroidPackage pkg) { - return ((AndroidPackageHidden) pkg).isVendor(); - } - - /** - * @deprecated Use {@link PackageState#isProduct} - */ - @Deprecated - public static boolean isProduct(@NonNull AndroidPackage pkg) { - return ((AndroidPackageHidden) pkg).isProduct(); - } - - /** - * @deprecated Use {@link PackageState#isOdm} - */ - @Deprecated - public static boolean isOdm(@NonNull AndroidPackage pkg) { - return ((AndroidPackageHidden) pkg).isOdm(); - } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 8bd2d94667f9..671e031b546b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -120,8 +120,11 @@ import com.android.internal.compat.IPlatformCompat; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.RoSystemProperties; +import com.android.internal.pm.permission.CompatibilityPermissionInfo; +import com.android.internal.pm.pkg.component.ComponentMutateUtils; import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedPermissionUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IntPair; @@ -144,8 +147,6 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedPermissionUtils; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 10b59c7230f6..a7ae4ebcb2eb 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -322,6 +322,14 @@ public interface PackageState { long[] getUsesSdkLibrariesVersionsMajor(); /** + * @see R.styleable#AndroidManifestUsesSdkLibrary_optional + * @hide + */ + @Immutable.Ignore + @NonNull + boolean[] getUsesSdkLibrariesOptional(); + + /** * @see R.styleable#AndroidManifestUsesStaticLibrary * @hide */ diff --git a/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java b/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java index 6cbc1de75010..6a156415982c 100644 --- a/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java +++ b/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java @@ -16,6 +16,8 @@ package com.android.server.pm.pkg; +import com.android.internal.pm.pkg.SEInfoUtil; + /** * Utility methods that need to be used in application space. * @hide @@ -23,10 +25,10 @@ package com.android.server.pm.pkg; public final class SELinuxUtil { /** Append to existing seinfo label for instant apps @hide */ - private static final String INSTANT_APP_STR = ":ephemeralapp"; + private static final String INSTANT_APP_STR = SEInfoUtil.INSTANT_APP_STR; /** Append to existing seinfo when modifications are complete @hide */ - public static final String COMPLETE_STR = ":complete"; + public static final String COMPLETE_STR = SEInfoUtil.COMPLETE_STR; /** @hide */ public static String getSeinfoUser(PackageUserState userState) { diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java index 532a7f8f893f..c9da99da7902 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java @@ -45,11 +45,13 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ComponentMutateUtils; import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedProviderImpl; import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.IntentResolver; @@ -62,8 +64,6 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedProviderImpl; import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4e5dc1dd76fa..938ed2329ffd 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -76,7 +76,6 @@ import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED; @@ -97,7 +96,6 @@ import static com.android.server.wm.WindowManagerPolicyProto.SCREEN_ON_FULLY; import static com.android.server.wm.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE; import android.accessibilityservice.AccessibilityService; -import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -124,7 +122,6 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; @@ -204,8 +201,6 @@ import android.widget.Toast; import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; -import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; -import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; import com.android.internal.display.BrightnessUtils; @@ -385,8 +380,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { public static final String TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD = "waitForAllWindowsDrawn"; - private static final String TALKBACK_LABEL = "TalkBack"; - private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800; /** @@ -477,6 +470,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** Controller that supports enabling an AccessibilityService by holding down the volume keys */ private AccessibilityShortcutController mAccessibilityShortcutController; + private TalkbackShortcutController mTalkbackShortcutController; + boolean mSafeMode; // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. @@ -813,7 +808,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { handleScreenShot(msg.arg1); break; case MSG_SWITCH_KEYBOARD_LAYOUT: - handleSwitchKeyboardLayout(msg.arg1, msg.arg2); + SwitchKeyboardLayoutMessageObject object = + (SwitchKeyboardLayoutMessageObject) msg.obj; + handleSwitchKeyboardLayout(object.keyEvent, object.direction, + object.focusedToken); break; case MSG_LOG_KEYBOARD_SYSTEM_EVENT: handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj); @@ -934,6 +932,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private record SwitchKeyboardLayoutMessageObject(KeyEvent keyEvent, IBinder focusedToken, + int direction) { + } + final IPersistentVrStateCallbacks mPersistentVrModeListener = new IPersistentVrStateCallbacks.Stub() { @Override @@ -1602,19 +1604,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (DEBUG_INPUT) { Slog.d(TAG, "Executing stem primary triple press action behavior."); } - - if (Settings.System.getIntForUser(mContext.getContentResolver(), - Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, - /* def= */ 0, UserHandle.USER_CURRENT) == 1) { - /** Toggle talkback begin */ - ComponentName componentName = getTalkbackComponent(); - if (componentName != null && toggleTalkBack(componentName)) { - /** log stem triple press telemetry if it's a talkback enabled event */ - logStemTriplePressAccessibilityTelemetry(componentName); - } - performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */ false, - /* reason = */ "Stem primary - Triple Press - Toggle Accessibility"); - /** Toggle talkback end */ + mTalkbackShortcutController.toggleTalkback(mCurrentUserId); + if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) { + performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */ + false, /* reason = */ + "Stem primary - Triple Press - Toggle Accessibility"); } break; } @@ -1640,61 +1634,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** - * A function that toggles talkback service - * - * @return {@code true} if talkback is enabled, {@code false} if talkback is disabled - */ - private boolean toggleTalkBack(ComponentName componentName) { - final Set<ComponentName> enabledServices = - AccessibilityUtils.getEnabledServicesFromSettings(mContext, mCurrentUserId); - - boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName); - AccessibilityUtils.setAccessibilityServiceState(mContext, componentName, - !isTalkbackAlreadyEnabled); - /** if isTalkbackAlreadyEnabled is true, then it's a disabled event so return false - * and if isTalkbackAlreadyEnabled is false, return true as it's an enabled event */ - return !isTalkbackAlreadyEnabled; - } - - /** - * A function that logs stem triple press accessibility telemetry - * If the user setup (Oobe) is not completed, set the - * WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE - * setting which will be later logged via Settings Snapshot - * else, log ACCESSIBILITY_SHORTCUT_REPORTED atom - */ - private void logStemTriplePressAccessibilityTelemetry(ComponentName componentName) { - if (!AccessibilityUtils.isUserSetupCompleted(mContext)) { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, 1); - } else { - AccessibilityStatsLogUtils.logAccessibilityShortcutActivated(mContext, componentName, - ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE, - /* serviceEnabled= */ true); - } - } - - private ComponentName getTalkbackComponent() { - AccessibilityManager accessibilityManager = mContext.getSystemService( - AccessibilityManager.class); - List<AccessibilityServiceInfo> serviceInfos = - accessibilityManager.getInstalledAccessibilityServiceList(); - - for (AccessibilityServiceInfo service : serviceInfos) { - final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; - if (isTalkback(serviceInfo)) { - return new ComponentName(serviceInfo.packageName, serviceInfo.name); - } - } - return null; - } - - private boolean isTalkback(ServiceInfo info) { - String label = info.loadLabel(mPackageManager).toString(); - return label.equals(TALKBACK_LABEL); - } - - /** * Load most recent task (expect current task) and bring it to the front. */ void performStemPrimaryDoublePressSwitchToRecentTask() { @@ -1731,12 +1670,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case TRIPLE_PRESS_PRIMARY_NOTHING: break; case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY: - if (Settings.System.getIntForUser( - mContext.getContentResolver(), - Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, - /* def= */ 0, - UserHandle.USER_CURRENT) - == 1) { + if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) { return 3; } break; @@ -2252,6 +2186,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { ButtonOverridePermissionChecker getButtonOverridePermissionChecker() { return new ButtonOverridePermissionChecker(); } + + TalkbackShortcutController getTalkbackShortcutController() { + return new TalkbackShortcutController(mContext); + } } /** {@inheritDoc} */ @@ -2515,6 +2453,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDrawnTimeout = mContext.getResources().getInteger( com.android.internal.R.integer.config_keyguardDrawnTimeout); mKeyguardDelegate = injector.getKeyguardServiceDelegate(); + mTalkbackShortcutController = injector.getTalkbackShortcutController(); initKeyCombinationRules(); initSingleKeyGestureRules(injector.getLooper()); mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker(); @@ -3709,7 +3648,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_LANGUAGE_SWITCH: if (firstDown) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - sendSwitchKeyboardLayout(event, direction); + sendSwitchKeyboardLayout(event, focusedToken, direction); logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH); return true; } @@ -3978,7 +3917,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { + ", policyFlags=" + policyFlags); } - if (interceptUnhandledKey(event)) { + if (interceptUnhandledKey(event, focusedToken)) { return null; } @@ -4036,7 +3975,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { return fallbackEvent; } - private boolean interceptUnhandledKey(KeyEvent event) { + private boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) { final int keyCode = event.getKeyCode(); final int repeatCount = event.getRepeatCount(); final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; @@ -4049,7 +3988,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) { int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - sendSwitchKeyboardLayout(event, direction); + sendSwitchKeyboardLayout(event, focusedToken, direction); return true; } } @@ -4105,16 +4044,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, int direction) { - mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, event.getDeviceId(), - direction).sendToTarget(); + private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, + @Nullable IBinder focusedToken, int direction) { + SwitchKeyboardLayoutMessageObject object = + new SwitchKeyboardLayoutMessageObject(event, focusedToken, direction); + mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, object).sendToTarget(); } - private void handleSwitchKeyboardLayout(int deviceId, int direction) { + private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction, + IBinder focusedToken) { if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { - InputMethodManagerInternal.get().switchKeyboardLayout(direction); + IBinder targetWindowToken = + mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken); + InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction, + event.getDisplayId(), targetWindowToken); } else { - mWindowManagerFuncs.switchKeyboardLayout(deviceId, direction); + mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); } } @@ -4124,7 +4069,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if ((actions & ACTION_PASS_TO_USER) != 0) { long delayMillis = interceptKeyBeforeDispatching( focusedToken, fallbackEvent, policyFlags); - if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent)) { + if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent, focusedToken)) { return true; } } diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java new file mode 100644 index 000000000000..906da2f4cdf5 --- /dev/null +++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.UserHandle; +import android.provider.Settings; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; +import com.android.internal.accessibility.util.AccessibilityUtils; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.List; +import java.util.Set; + +/** + * This class controls talkback shortcut related operations such as toggling, quering and + * logging. + */ +@VisibleForTesting +class TalkbackShortcutController { + private static final String TALKBACK_LABEL = "TalkBack"; + private final Context mContext; + private final PackageManager mPackageManager; + + TalkbackShortcutController(Context context) { + mContext = context; + mPackageManager = mContext.getPackageManager(); + } + + /** + * A function that toggles talkback service. + * + * @return talkback state after toggle. {@code true} if talkback is enabled, {@code false} if + * talkback is disabled + */ + boolean toggleTalkback(int userId) { + final Set<ComponentName> enabledServices = + AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId); + ComponentName componentName = getTalkbackComponent(); + boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName); + + if (isTalkBackShortcutGestureEnabled()) { + isTalkbackAlreadyEnabled = !isTalkbackAlreadyEnabled; + AccessibilityUtils.setAccessibilityServiceState(mContext, componentName, + isTalkbackAlreadyEnabled); + + // log stem triple press telemetry if it's a talkback enabled event. + if (componentName != null && isTalkbackAlreadyEnabled) { + logStemTriplePressAccessibilityTelemetry(componentName); + } + } + return isTalkbackAlreadyEnabled; + } + + private ComponentName getTalkbackComponent() { + AccessibilityManager accessibilityManager = mContext.getSystemService( + AccessibilityManager.class); + List<AccessibilityServiceInfo> serviceInfos = + accessibilityManager.getInstalledAccessibilityServiceList(); + + for (AccessibilityServiceInfo service : serviceInfos) { + final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; + if (isTalkback(serviceInfo)) { + return new ComponentName(serviceInfo.packageName, serviceInfo.name); + } + } + return null; + } + + boolean isTalkBackShortcutGestureEnabled() { + return Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, + /* def= */ 0, UserHandle.USER_CURRENT) == 1; + } + + /** + * A function that logs stem triple press accessibility telemetry. If the user setup (Oobe) + * is not completed, set the WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE setting which + * will be later logged via Settings Snapshot else, log ACCESSIBILITY_SHORTCUT_REPORTED atom + */ + private void logStemTriplePressAccessibilityTelemetry(ComponentName componentName) { + if (!AccessibilityUtils.isUserSetupCompleted(mContext)) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, 1); + return; + } + AccessibilityStatsLogUtils.logAccessibilityShortcutActivated(mContext, + componentName, + ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE, + /* serviceEnabled= */ true); + } + + private boolean isTalkback(ServiceInfo info) { + return TALKBACK_LABEL.equals(info.loadLabel(mPackageManager).toString()); + } +} diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java index 99653ae1cd72..24d7acd772c1 100644 --- a/services/core/java/com/android/server/power/ThermalManagerService.java +++ b/services/core/java/com/android/server/power/ThermalManagerService.java @@ -759,6 +759,36 @@ public class ThermalManagerService extends SystemService { case "NPU": type = Temperature.TYPE_NPU; break; + case "TPU": + type = Temperature.TYPE_TPU; + break; + case "DISPLAY": + type = Temperature.TYPE_DISPLAY; + break; + case "MODEM": + type = Temperature.TYPE_MODEM; + break; + case "SOC": + type = Temperature.TYPE_SOC; + break; + case "WIFI": + type = Temperature.TYPE_WIFI; + break; + case "CAMERA": + type = Temperature.TYPE_CAMERA; + break; + case "FLASHLIGHT": + type = Temperature.TYPE_FLASHLIGHT; + break; + case "SPEAKER": + type = Temperature.TYPE_SPEAKER; + break; + case "AMBIENT": + type = Temperature.TYPE_AMBIENT; + break; + case "POGO": + type = Temperature.TYPE_POGO; + break; default: pw.println("Invalid temperature type: " + typeName); return -1; diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java index 43fd15d690e6..6fbbc0f072e8 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -19,7 +19,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.os.BatteryConsumer; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; import java.lang.annotation.Retention; diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java index 5fd8ddfbf240..7feb9643fb8f 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java @@ -19,7 +19,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; import java.util.ArrayList; diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java index ecfed537b798..935695008a9a 100644 --- a/core/java/com/android/internal/os/MultiStateStats.java +++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package com.android.internal.os; +package com.android.server.power.stats; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.LongArrayMultiStateCounter; import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 0facb9c01d74..1637022f705d 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.util.IndentingPrintWriter; import android.util.SparseArray; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; 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/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java index 1f6f11320f1b..c267b799e2a4 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -22,7 +22,6 @@ import android.os.BatteryUsageStats; import android.os.UidBatteryConsumer; import android.util.Slog; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; import java.util.ArrayList; 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/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index eac4fc00b667..9a85c42e1a10 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -1608,14 +1608,12 @@ public class TrustManagerService extends SystemService { user.name, user.id, user.flags); if (!user.supportsSwitchToByUser()) { final boolean locked; - if (user.isProfile()) { - if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) { - fout.print(" (profile with separate challenge)"); - locked = isDeviceLockedInner(user.id); - } else { - fout.print(" (profile with unified challenge)"); - locked = isDeviceLockedInner(resolveProfileParent(user.id)); - } + if (mLockPatternUtils.isProfileWithUnifiedChallenge(user.id)) { + fout.print(" (profile with unified challenge)"); + locked = isDeviceLockedInner(resolveProfileParent(user.id)); + } else if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) { + fout.print(" (profile with separate challenge)"); + locked = isDeviceLockedInner(user.id); } else { fout.println(" (user that cannot be switched to)"); locked = isDeviceLockedInner(user.id); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e4c7fc1f3797..d903ad4d9f0d 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -41,7 +41,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; -import android.content.res.Resources; import android.graphics.Rect; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; @@ -1851,14 +1850,14 @@ public final class TvInputManagerService extends SystemService { sessionState.currentChannel = channelUri; notifyCurrentChannelInfosUpdatedLocked(userState); if (!sessionState.isRecordingSession) { - String actualInputId = getActualInputId(sessionState); - if (!TextUtils.equals(mOnScreenInputId, actualInputId)) { + String sessionActualInputId = getSessionActualInputId(sessionState); + if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) { logExternalInputEvent( FrameworkStatsLog .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED, - actualInputId, sessionState); + sessionActualInputId, sessionState); } - mOnScreenInputId = actualInputId; + mOnScreenInputId = sessionActualInputId; mOnScreenSessionState = sessionState; } } @@ -2081,6 +2080,45 @@ public final class TvInputManagerService extends SystemService { } @Override + public void stopPlayback(IBinder sessionToken, int mode, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "stopPlayback"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).stopPlayback( + mode); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in stopPlayback(mode)", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void startPlayback(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "stopPlayback"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).startPlayback(); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in startPlayback()", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, @@ -2985,11 +3023,20 @@ public final class TvInputManagerService extends SystemService { // e.g. if an HDMI port has a CEC device plugged in, the actual input id of the HDMI // session should be the input id of CEC device instead of the default HDMI input id. @GuardedBy("mLock") - private String getActualInputId(SessionState sessionState) { + private String getSessionActualInputId(SessionState sessionState) { UserState userState = getOrCreateUserStateLocked(sessionState.userId); TvInputState tvInputState = userState.inputMap.get(sessionState.inputId); + if (tvInputState == null) { + Slog.w(TAG, "No TvInputState for sessionState.inputId " + sessionState.inputId); + return sessionState.inputId; + } TvInputInfo tvInputInfo = tvInputState.info; - String actualInputId = sessionState.inputId; + if (tvInputInfo == null) { + Slog.w(TAG, "TvInputInfo is null for input id " + sessionState.inputId); + return sessionState.inputId; + } + + String sessionActualInputId = sessionState.inputId; switch (tvInputInfo.getType()) { case TvInputInfo.TYPE_HDMI: // TODO: find a better approach towards active CEC device in future @@ -2997,13 +3044,13 @@ public final class TvInputManagerService extends SystemService { mTvInputHardwareManager.getHdmiParentInputMap(); if (hdmiParentInputMap.containsKey(sessionState.inputId)) { List<String> parentInputList = hdmiParentInputMap.get(sessionState.inputId); - actualInputId = parentInputList.get(0); + sessionActualInputId = parentInputList.get(0); } break; default: break; } - return actualInputId; + return sessionActualInputId; } @Nullable @@ -3111,7 +3158,21 @@ public final class TvInputManagerService extends SystemService { private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) { UserState userState = getOrCreateUserStateLocked(sessionState.userId); TvInputState tvInputState = userState.inputMap.get(inputId); + if (tvInputState == null) { + Slog.w(TAG, "Cannot find input state for input id " + inputId); + // If input id is not found, try to find the input id of this sessionState. + inputId = sessionState.inputId; + tvInputState = userState.inputMap.get(inputId); + } + if (tvInputState == null) { + Slog.w(TAG, "Cannot find input state for sessionState.inputId " + inputId); + return; + } TvInputInfo tvInputInfo = tvInputState.info; + if (tvInputInfo == null) { + Slog.w(TAG, "TvInputInfo is null for input id " + inputId); + return; + } int inputState = tvInputState.state; int inputType = tvInputInfo.getType(); String displayName = tvInputInfo.loadLabel(mContext).toString(); @@ -3647,14 +3708,14 @@ public final class TvInputManagerService extends SystemService { mSessionState.currentChannel = channelUri; notifyCurrentChannelInfosUpdatedLocked(userState); if (!mSessionState.isRecordingSession) { - String actualInputId = getActualInputId(mSessionState); - if (!TextUtils.equals(mOnScreenInputId, actualInputId)) { + String sessionActualInputId = getSessionActualInputId(mSessionState); + if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) { logExternalInputEvent( FrameworkStatsLog .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED, - actualInputId, mSessionState); + sessionActualInputId, mSessionState); } - mOnScreenInputId = actualInputId; + mOnScreenInputId = sessionActualInputId; mOnScreenSessionState = mSessionState; } } diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java index 2b6dffb2b271..7b5192c4bd6b 100644 --- a/services/core/java/com/android/server/utils/AnrTimer.java +++ b/services/core/java/com/android/server/utils/AnrTimer.java @@ -16,37 +16,22 @@ package com.android.server.utils; -import static android.text.TextUtils.formatSimple; - -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.Handler; -import android.os.Looper; import android.os.Message; -import android.os.Process; import android.os.SystemClock; import android.os.Trace; -import android.text.TextUtils; import android.text.format.TimeMigrationUtils; -import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; -import android.util.MathUtils; -import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.Keep; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.ProcessCpuTracker; import com.android.internal.util.RingBuffer; import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; /** * This class managers AnrTimers. An AnrTimer is a substitute for a delayed Message. In legacy @@ -102,12 +87,6 @@ public class AnrTimer<V> { private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER; /** - * Enable tracing from the time a timer expires until it is accepted or discarded. This is - * used to diagnose long latencies in the client. - */ - private static final boolean ENABLE_TRACING = false; - - /** * Return true if the feature is enabled. By default, the value is take from the Flags class * but it can be changed for local testing. */ @@ -116,22 +95,13 @@ public class AnrTimer<V> { } /** - * The status of an ANR timer. TIMER_INVALID status is returned when an error is detected. + * This class allows test code to provide instance-specific overrides. */ - private static final int TIMER_INVALID = 0; - private static final int TIMER_RUNNING = 1; - private static final int TIMER_EXPIRED = 2; - - @IntDef(prefix = { "TIMER_" }, value = { - TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED - }) - private @interface TimerStatus {} - - /** - * A static list of all known AnrTimer instances, used for dumping and testing. - */ - @GuardedBy("sAnrTimerList") - private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>(); + static class Injector { + boolean anrTimerServiceEnabled() { + return AnrTimer.anrTimerServiceEnabled(); + } + } /** * An error is defined by its issue, the operation that detected the error, the tag of the @@ -161,6 +131,22 @@ public class AnrTimer<V> { this.arg = arg; this.timestamp = SystemClock.elapsedRealtime(); } + + /** + * Dump a single error to the output stream. + */ + private void dump(IndentingPrintWriter ipw, int seq) { + ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, operation, tag, issue, arg); + + final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); + final long etime = offset + timestamp; + ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime)); + ipw.increaseIndent(); + for (int i = 0; i < stack.length; i++) { + ipw.println(" " + stack[i].toString()); + } + ipw.decreaseIndent(); + } } /** @@ -171,132 +157,10 @@ public class AnrTimer<V> { @GuardedBy("sErrors") private static final RingBuffer<Error> sErrors = new RingBuffer<>(Error.class, 20); - /** - * A record of a single anr timer. The pid and uid are retained for reference but they do not - * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer} - * through the owner field. Access to timer fields is guarded by the mLock of the owner. - */ - private static class Timer { - /** The AnrTimer that is managing this Timer. */ - final AnrTimer owner; - - /** The argument that uniquely identifies the Timer in the context of its current owner. */ - final Object arg; - /** The pid of the process being tracked by this Timer. */ - final int pid; - /** The uid of the process being tracked by this Timer as reported by the kernel. */ - final int uid; - /** The original timeout. */ - final long timeoutMs; - - /** The status of the Timer. */ - @GuardedBy("owner.mLock") - @TimerStatus - int status; - - /** The absolute time the timer was startd */ - final long startedMs; - - /** Fields used by the native timer service. */ - - /** The timer ID: used to exchange information with the native service. */ - int timerId; - - /** Fields used by the legacy timer service. */ - - /** - * The process's cpu delay time when the timer starts . It is meaningful only if - * extendable is true. The cpu delay is cumulative, so the incremental delay that occurs - * during a timer is the delay at the end of the timer minus this value. Units are in - * milliseconds. - */ - @GuardedBy("owner.mLock") - long initialCpuDelayMs; - - /** True if the timer has been extended. */ - @GuardedBy("owner.mLock") - boolean extended; - - /** - * Fetch a new Timer. This is private. Clients should get a new timer using the obtain() - * method. - */ - private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs, - @NonNull AnrTimer service) { - this.arg = arg; - this.pid = pid; - this.uid = uid; - this.timerId = 0; - this.timeoutMs = timeoutMs; - this.startedMs = now(); - this.owner = service; - this.initialCpuDelayMs = 0; - this.extended = false; - this.status = TIMER_INVALID; - } - - /** Get a timer. This implementation constructs a new timer. */ - static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout, - @NonNull AnrTimer service) { - return new Timer(pid, uid, arg, timeout, service); - } - - /** Release a timer. This implementation simply drops the timer. */ - void release() { - } - - /** Return the age of the timer. This is used for debugging. */ - long age() { - return now() - startedMs; - } - - /** - * The hash code is generated from the owner and the argument. By definition, the - * combination must be unique for the lifetime of an in-use Timer. - */ - @Override - public int hashCode() { - return Objects.hash(owner, arg); - } - - /** - * The equality check compares the owner and the argument. By definition, the combination - * must be unique for the lifetime of an in-use Timer. - */ - @Override - public boolean equals(Object r) { - if (r instanceof Timer) { - Timer t = (Timer) r; - return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg); - } - return false; - } - - @Override - public String toString() { - final int myStatus; - synchronized (owner.mLock) { - myStatus = status; - } - return "timerId=" + timerId + " pid=" + pid + " uid=" + uid - + " " + statusString(myStatus) + " " + owner.mLabel; - } - } - /** A lock for the AnrTimer instance. */ private final Object mLock = new Object(); /** - * The map from client argument to the associated timer. - */ - @GuardedBy("mLock") - private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>(); - - /** The highwater mark of started, but not closed, timers. */ - @GuardedBy("mLock") - private int mMaxStarted = 0; - - /** * The total number of timers started. */ @GuardedBy("mLock") @@ -309,176 +173,6 @@ public class AnrTimer<V> { private int mTotalErrors = 0; /** - * The total number of timers that have expired. - */ - @GuardedBy("mLock") - private int mTotalExpired = 0; - - /** - * A TimerService that generates a timeout event <n> milliseconds in the future. See the - * class documentation for an explanation of the operations. - */ - private abstract class TimerService { - /** Start a timer. The timeout must be initialized. */ - abstract boolean start(@NonNull Timer timer); - - abstract void cancel(@NonNull Timer timer); - - abstract void accept(@NonNull Timer timer); - - abstract void discard(@NonNull Timer timer); - } - - /** - * A class to assist testing. All methods are null by default but can be overridden as - * necessary for a test. - */ - @VisibleForTesting - static class Injector { - private final Handler mReferenceHandler; - - Injector(@NonNull Handler handler) { - mReferenceHandler = handler; - } - - /** - * Return a handler for the given Callback, based on the reference handler. The handler - * might be mocked, in which case it does not have a valid Looper. In this case, use the - * main Looper. - */ - @NonNull - Handler newHandler(@NonNull Handler.Callback callback) { - Looper looper = mReferenceHandler.getLooper(); - if (looper == null) looper = Looper.getMainLooper(); - return new Handler(looper, callback); - } - - /** - * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes - * for unit tests. - **/ - @NonNull - CpuTracker newTracker() { - return new CpuTracker(); - } - - /** Return true if the feature is enabled. */ - boolean isFeatureEnabled() { - return anrTimerServiceEnabled(); - } - } - - /** - * A helper class to measure CPU delays. Given a process ID, this class will return the - * cumulative CPU delay for the PID, since process inception. This class is defined to assist - * testing. - */ - @VisibleForTesting - static class CpuTracker { - /** - * The parameter to ProcessCpuTracker indicates that statistics should be collected on a - * single process and not on the collection of threads associated with that process. - */ - private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false); - - /** A simple wrapper to fetch the delay. This method can be overridden for testing. */ - long delay(int pid) { - return mCpu.getCpuDelayTimeForPid(pid); - } - } - - /** - * The "user-space" implementation of the timer service. This service uses its own message - * handler to create timeouts. - */ - private class HandlerTimerService extends TimerService { - /** The lock for this handler */ - private final Object mLock = new Object(); - - /** The message handler for scheduling future events. */ - private final Handler mHandler; - - /** The interface to fetch process statistics that might extend an ANR timeout. */ - private final CpuTracker mCpu; - - /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */ - @VisibleForTesting - HandlerTimerService(@NonNull Injector injector) { - mHandler = injector.newHandler(this::expires); - mCpu = injector.newTracker(); - } - - /** Post a message with the specified timeout. The timer is not modified. */ - private void post(@NonNull Timer t, long timeoutMillis) { - final Message msg = mHandler.obtainMessage(); - msg.obj = t; - mHandler.sendMessageDelayed(msg, timeoutMillis); - } - - /** - * The local expiration handler first attempts to compute a timer extension. If the timer - * should be extended, it is rescheduled in the future (granting more time to the - * associated process). If the timer should not be extended then the timeout is delivered - * to the client. - * - * A process is extended to account for the time the process was swapped out and was not - * runnable through no fault of its own. A timer can only be extended once and only if - * the AnrTimer permits extensions. Finally, a timer will never be extended by more than - * the original timeout, so the total timeout will never be more than twice the originally - * configured timeout. - */ - private boolean expires(Message msg) { - Timer t = (Timer) msg.obj; - synchronized (mLock) { - long extension = 0; - if (mExtend && !t.extended) { - extension = mCpu.delay(t.pid) - t.initialCpuDelayMs; - if (extension < 0) extension = 0; - if (extension > t.timeoutMs) extension = t.timeoutMs; - t.extended = true; - } - if (extension > 0) { - post(t, extension); - } else { - onExpiredLocked(t); - } - } - return true; - } - - @GuardedBy("mLock") - @Override - boolean start(@NonNull Timer t) { - if (mExtend) { - t.initialCpuDelayMs = mCpu.delay(t.pid); - } - post(t, t.timeoutMs); - return true; - } - - @Override - void cancel(@NonNull Timer t) { - mHandler.removeMessages(0, t); - } - - @Override - void accept(@NonNull Timer t) { - // Nothing to do. - } - - @Override - void discard(@NonNull Timer t) { - // Nothing to do. - } - - /** The string identifies this subclass of AnrTimerService as being based on handlers. */ - @Override - public String toString() { - return "handler"; - } - } - - /** * The handler for messages sent from this instance. */ private final Handler mHandler; @@ -499,18 +193,6 @@ public class AnrTimer<V> { private final boolean mExtend; /** - * The timer service to use for this AnrTimer. - */ - private final TimerService mTimerService; - - /** - * Whether or not canceling a non-existent timer is an error. Clients often cancel freely - * preemptively, without knowing if the timer was ever started. Keeping this variable true - * means that such behavior is not an error. - */ - private final boolean mLenientCancel = true; - - /** * The top-level switch for the feature enabled or disabled. */ private final FeatureSwitch mFeature; @@ -528,40 +210,34 @@ public class AnrTimer<V> { * AnrTimer may extend the individual timer rather than immediately delivering the timeout to * the client. The extension policy is not part of the instance. * - * This method accepts an {@link #Injector} to tune behavior for testing. This method should - * not be called directly by regular clients. - * * @param handler The handler to which the expiration message will be delivered. * @param what The "what" parameter for the expiration message. * @param label A name for this instance. * @param extend A flag to indicate if expired timers can be granted extensions. - * @param injector An {@link #Injector} to tune behavior for testing. + * @param injector An injector to provide overrides for testing. */ @VisibleForTesting AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend, - @NonNull Injector injector) { + @NonNull Injector injector) { mHandler = handler; mWhat = what; mLabel = label; mExtend = extend; - boolean enabled = injector.isFeatureEnabled(); - if (!enabled) { - mFeature = new FeatureDisabled(); - mTimerService = null; - } else { - mFeature = new FeatureEnabled(); - mTimerService = new HandlerTimerService(injector); - - synchronized (sAnrTimerList) { - sAnrTimerList.add(new WeakReference(this)); - } - } - Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService, label)); + mFeature = new FeatureDisabled(); } /** - * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler, - * int, String, boolean, Injector} for a functional description. + * Create one AnrTimer instance. The instance is given a handler and a "what". Individual + * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent + * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set + * to the timer key. + * + * AnrTimer instances have a label, which must be unique. The label is used for reporting and + * debug. + * + * If an individual timer expires internally, and the "extend" parameter is true, then the + * AnrTimer may extend the individual timer rather than immediately delivering the timeout to + * the client. The extension policy is not part of the instance. * * @param handler The handler to which the expiration message will be delivered. * @param what The "what" parameter for the expiration message. @@ -569,7 +245,7 @@ public class AnrTimer<V> { * @param extend A flag to indicate if expired timers can be granted extensions. */ public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) { - this(handler, what, label, extend, new Injector(handler)); + this(handler, what, label, extend, new Injector()); } /** @@ -596,105 +272,17 @@ public class AnrTimer<V> { } /** - * Start a trace on the timer. The trace is laid down in the AnrTimerTrack. - */ - private void traceBegin(Timer t, String what) { - if (ENABLE_TRACING) { - final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel); - final int cookie = t.hashCode(); - Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie); - } - } - - /** - * End a trace on the timer. - */ - private void traceEnd(Timer t) { - if (ENABLE_TRACING) { - final int cookie = t.hashCode(); - Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie); - } - } - - /** - * Return the string representation for a timer status. - */ - private static String statusString(int s) { - switch (s) { - case TIMER_INVALID: return "invalid"; - case TIMER_RUNNING: return "running"; - case TIMER_EXPIRED: return "expired"; - } - return formatSimple("unknown: %d", s); - } - - /** - * Delete the timer associated with arg from the maps and return it. Return null if the timer - * was not found. - */ - @GuardedBy("mLock") - private Timer removeLocked(V arg) { - Timer timer = mTimerMap.remove(arg); - return timer; - } - - /** - * Return the number of timers currently running. - */ - @VisibleForTesting - static int sizeOfTimerList() { - synchronized (sAnrTimerList) { - int totalTimers = 0; - for (int i = 0; i < sAnrTimerList.size(); i++) { - AnrTimer client = sAnrTimerList.get(i).get(); - if (client != null) totalTimers += client.mTimerMap.size(); - } - return totalTimers; - } - } - - /** - * Clear out all existing timers. This will lead to unexpected behavior if used carelessly. - * It is available only for testing. It returns the number of times that were actually - * erased. - */ - @VisibleForTesting - static int resetTimerListForHermeticTest() { - synchronized (sAnrTimerList) { - int mapLen = 0; - for (int i = 0; i < sAnrTimerList.size(); i++) { - AnrTimer client = sAnrTimerList.get(i).get(); - if (client != null) { - mapLen += client.mTimerMap.size(); - client.mTimerMap.clear(); - } - } - if (mapLen > 0) { - Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen)); - } - return mapLen; - } - } - - /** - * Generate a log message for a timer. - */ - private void report(@NonNull Timer timer, @NonNull String msg) { - Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg)); - } - - /** * The FeatureSwitch class provides a quick switch between feature-enabled behavior and * feature-disabled behavior. */ private abstract class FeatureSwitch { - abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs); + abstract void start(@NonNull V arg, int pid, int uid, long timeoutMs); - abstract boolean cancel(@NonNull V arg); + abstract void cancel(@NonNull V arg); - abstract boolean accept(@NonNull V arg); + abstract void accept(@NonNull V arg); - abstract boolean discard(@NonNull V arg); + abstract void discard(@NonNull V arg); abstract boolean enabled(); } @@ -706,29 +294,25 @@ public class AnrTimer<V> { private class FeatureDisabled extends FeatureSwitch { /** Start a timer by sending a message to the client's handler. */ @Override - boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { + void start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Message msg = mHandler.obtainMessage(mWhat, arg); mHandler.sendMessageDelayed(msg, timeoutMs); - return true; } /** Cancel a timer by removing the message from the client's handler. */ @Override - boolean cancel(@NonNull V arg) { + void cancel(@NonNull V arg) { mHandler.removeMessages(mWhat, arg); - return true; } /** accept() is a no-op when the feature is disabled. */ @Override - boolean accept(@NonNull V arg) { - return true; + void accept(@NonNull V arg) { } /** discard() is a no-op when the feature is disabled. */ @Override - boolean discard(@NonNull V arg) { - return true; + void discard(@NonNull V arg) { } /** The feature is not enabled. */ @@ -739,113 +323,6 @@ public class AnrTimer<V> { } /** - * The FeatureEnabled class enables the AnrTimer logic. It is used when the AnrTimer service - * is enabled via Flags.anrTimerServiceEnabled. - */ - private class FeatureEnabled extends FeatureSwitch { - - /** - * Start a timer. - */ - @Override - boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { - final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this); - synchronized (mLock) { - Timer old = mTimerMap.get(arg); - // There is an existing timer. If the timer was running, then cancel the running - // timer and restart it. If the timer was expired record a protocol error and - // discard the expired timer. - if (old != null) { - if (old.status == TIMER_EXPIRED) { - restartedLocked(old.status, arg); - discard(arg); - } else { - cancel(arg); - } - } - if (mTimerService.start(timer)) { - timer.status = TIMER_RUNNING; - mTimerMap.put(arg, timer); - mTotalStarted++; - mMaxStarted = Math.max(mMaxStarted, mTimerMap.size()); - if (DEBUG) report(timer, "start"); - return true; - } else { - Log.e(TAG, "AnrTimer.start failed"); - return false; - } - } - } - - /** - * Cancel a timer. Return false if the timer was not found. - */ - @Override - boolean cancel(@NonNull V arg) { - synchronized (mLock) { - Timer timer = removeLocked(arg); - if (timer == null) { - if (!mLenientCancel) notFoundLocked("cancel", arg); - return false; - } - mTimerService.cancel(timer); - // There may be an expiration message in flight. Cancel it. - mHandler.removeMessages(mWhat, arg); - if (DEBUG) report(timer, "cancel"); - timer.release(); - return true; - } - } - - /** - * Accept a timer in the framework-level handler. The timeout has been accepted and the - * timeout handler is executing. Return false if the timer was not found. - */ - @Override - boolean accept(@NonNull V arg) { - synchronized (mLock) { - Timer timer = removeLocked(arg); - if (timer == null) { - notFoundLocked("accept", arg); - return false; - } - mTimerService.accept(timer); - traceEnd(timer); - if (DEBUG) report(timer, "accept"); - timer.release(); - return true; - } - } - - /** - * Discard a timer in the framework-level handler. For whatever reason, the timer is no - * longer interesting. No statistics are collected. Return false if the time was not - * found. - */ - @Override - boolean discard(@NonNull V arg) { - synchronized (mLock) { - Timer timer = removeLocked(arg); - if (timer == null) { - notFoundLocked("discard", arg); - return false; - } - mTimerService.discard(timer); - traceEnd(timer); - if (DEBUG) report(timer, "discard"); - timer.release(); - return true; - } - } - - /** The feature is enabled. */ - @Override - boolean enabled() { - return true; - } - } - - /** * Start a timer associated with arg. The same object must be used to cancel, accept, or * discard a timer later. If a timer already exists with the same arg, then the existing timer * is canceled and a new timer is created. @@ -854,32 +331,27 @@ public class AnrTimer<V> { * @param pid The Linux process ID of the target being timed. * @param uid The Linux user ID of the target being timed. * @param timeoutMs The timer timeout, in milliseconds. - * @return true if the timer was successfully created. */ - public boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { - return mFeature.start(arg, pid, uid, timeoutMs); + public void start(@NonNull V arg, int pid, int uid, long timeoutMs) { + mFeature.start(arg, pid, uid, timeoutMs); } /** * Cancel the running timer associated with arg. The timer is forgotten. If the timer has * expired, the call is treated as a discard. No errors are reported if the timer does not * exist or if the timer has expired. - * - * @return true if the timer was found and was running. */ - public boolean cancel(@NonNull V arg) { - return mFeature.cancel(arg); + public void cancel(@NonNull V arg) { + mFeature.cancel(arg); } /** * Accept the expired timer associated with arg. This indicates that the caller considers the * timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is * an error to accept a running timer, however the running timer will be canceled. - * - * @return true if the timer was found and was expired. */ - public boolean accept(@NonNull V arg) { - return mFeature.accept(arg); + public void accept(@NonNull V arg) { + mFeature.accept(arg); } /** @@ -889,24 +361,9 @@ public class AnrTimer<V> { * such a process could be stopped at a breakpoint and its failure to respond would not be an * error. It is an error to discard a running timer, however the running timer will be * canceled. - * - * @return true if the timer was found and was expired. */ - public boolean discard(@NonNull V arg) { - return mFeature.discard(arg); - } - - /** - * The notifier that a timer has fired. The timer is not modified. - */ - @GuardedBy("mLock") - private void onExpiredLocked(@NonNull Timer timer) { - if (DEBUG) report(timer, "expire"); - traceBegin(timer, "expired"); - mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg)); - synchronized (mLock) { - mTotalExpired++; - } + public void discard(@NonNull V arg) { + mFeature.discard(arg); } /** @@ -916,9 +373,7 @@ public class AnrTimer<V> { synchronized (mLock) { pw.format("timer: %s\n", mLabel); pw.increaseIndent(); - pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n", - mTotalStarted, mMaxStarted, mTimerMap.size(), - mTotalExpired, mTotalErrors); + pw.format("started=%d errors=%d\n", mTotalStarted, mTotalErrors); pw.decreaseIndent(); } } @@ -931,10 +386,20 @@ public class AnrTimer<V> { } /** - * The current time in milliseconds. + * Dump all errors to the output stream. */ - private static long now() { - return SystemClock.uptimeMillis(); + private static void dumpErrors(IndentingPrintWriter ipw) { + Error errors[]; + synchronized (sErrors) { + if (sErrors.size() == 0) return; + errors = sErrors.toArray(); + } + ipw.println("Errors"); + ipw.increaseIndent(); + for (int i = 0; i < errors.length; i++) { + if (errors[i] != null) errors[i].dump(ipw, i); + } + ipw.decreaseIndent(); } /** @@ -966,60 +431,12 @@ public class AnrTimer<V> { } /** - * Log an error about a timer that is started when there is an existing timer. - */ - @GuardedBy("mLock") - private void restartedLocked(@TimerStatus int status, Object arg) { - recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg); - } - - /** - * Dump a single error to the output stream. - */ - private static void dump(IndentingPrintWriter ipw, int seq, Error err) { - ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag, - err.issue, err.arg); - - final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); - final long etime = offset + err.timestamp; - ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime)); - ipw.increaseIndent(); - for (int i = 0; i < err.stack.length; i++) { - ipw.println(" " + err.stack[i].toString()); - } - ipw.decreaseIndent(); - } - - /** - * Dump all errors to the output stream. - */ - private static void dumpErrors(IndentingPrintWriter ipw) { - Error errors[]; - synchronized (sErrors) { - if (sErrors.size() == 0) return; - errors = sErrors.toArray(); - } - ipw.println("Errors"); - ipw.increaseIndent(); - for (int i = 0; i < errors.length; i++) { - if (errors[i] != null) dump(ipw, i, errors[i]); - } - ipw.decreaseIndent(); - } - - /** * Dumpsys output. */ public static void dump(@NonNull PrintWriter pw, boolean verbose) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw); ipw.println("AnrTimer statistics"); ipw.increaseIndent(); - synchronized (sAnrTimerList) { - for (int i = 0; i < sAnrTimerList.size(); i++) { - AnrTimer client = sAnrTimerList.get(i).get(); - if (client != null) client.dump(ipw); - } - } if (verbose) dumpErrors(ipw); ipw.format("AnrTimerEnd\n"); ipw.decreaseIndent(); 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/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java index 81e5fbd564e0..769f01c34e17 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java @@ -142,8 +142,11 @@ public class ActivityMetricsLaunchObserver { * if the launching activity is started from an existing launch sequence (trampoline) * but cannot coalesce to the existing one, e.g. to a different display. * @param name The launching activity name. + * @param temperature The temperature at which a launch sequence had started. + * @param userId The id of the user the activity is being launched for. */ - public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature) { + public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature, + int userId) { } /** @@ -177,13 +180,15 @@ public class ActivityMetricsLaunchObserver { * @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds. * To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted} * from {@code timestampNanos}. + * @param launchMode The activity launch mode. * * @apiNote The finishing activity isn't necessarily the same as the starting activity; * in the case of a trampoline, multiple activities could've been started * and only the latest activity that was top-most during first-frame drawn * is reported here. */ - public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos) { + public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos, + int launchMode) { } /** diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 7b20529ce5e1..78f501ad9fed 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -1737,7 +1737,8 @@ class ActivityMetricsLogger { // Beginning a launch is timing sensitive and so should be observed as soon as possible. mLaunchObserver.onActivityLaunched(info.mLaunchingState.mStartUptimeNs, - info.mLastLaunchedActivity.mActivityComponent, temperature); + info.mLastLaunchedActivity.mActivityComponent, temperature, + info.mLastLaunchedActivity.mUserId); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } @@ -1774,7 +1775,8 @@ class ActivityMetricsLogger { "MetricsLogger:launchObserverNotifyActivityLaunchFinished"); mLaunchObserver.onActivityLaunchFinished(info.mLaunchingState.mStartUptimeNs, - info.mLastLaunchedActivity.mActivityComponent, timestampNs); + info.mLastLaunchedActivity.mActivityComponent, timestampNs, + info.mLastLaunchedActivity.launchMode); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0d06f5b256b0..04ac9fbf178d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -562,7 +562,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean mClientVisibilityDeferred;// was the visibility change message to client deferred? boolean idle; // has the activity gone idle? boolean hasBeenLaunched;// has this activity ever been launched? - boolean frozenBeforeDestroy;// has been frozen but not yet destroyed. boolean immersive; // immersive mode (don't interrupt if possible) boolean forceNewConfig; // force re-create with new config next time boolean supportsEnterPipOnTaskSwitch; // This flag is set by the system to indicate that the @@ -1201,8 +1200,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(" noDisplay="); pw.print(noDisplay); pw.print(" immersive="); pw.print(immersive); pw.print(" launchMode="); pw.println(launchMode); - pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); - pw.print(" forceNewConfig="); pw.println(forceNewConfig); pw.print(prefix); pw.print("mActivityType="); pw.println(activityTypeToString(getActivityType())); pw.print(prefix); pw.print("mImeInsetsFrozenUntilStartInput="); @@ -3848,7 +3845,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // updated for restoring original orientation of the display. if (next == null) { mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(), - false /* markFrozenIfConfigChanged */, true /* deferResume */); + true /* deferResume */); } if (activityRemoved) { mRootWindowContainer.resumeFocusedTasksTopActivities(); @@ -4090,7 +4087,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A cleanUpSplashScreen(); deferRelaunchUntilPaused = false; - frozenBeforeDestroy = false; if (setState) { setState(DESTROYED, "cleanUp"); @@ -6276,7 +6272,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void handleAlreadyVisible() { - stopFreezingScreenLocked(false); try { if (returningOptions != null) { app.getThread().scheduleOnNewActivityOptions(token, returningOptions.toBundle()); @@ -6710,19 +6705,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A stopFreezingScreen(true, true); } - void stopFreezingScreenLocked(boolean force) { - if (force || frozenBeforeDestroy) { - frozenBeforeDestroy = false; - if (getParent() == null) { - return; - } - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Clear freezing of %s: visible=%b freezing=%b", token, - isVisible(), isFreezingScreen()); - stopFreezingScreen(true, force); - } - } - void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) { if (!mFreezingScreen) { return; @@ -9569,7 +9551,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (finishing) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter " + "in finishing %s", this); - stopFreezingScreenLocked(false); return true; } @@ -9660,7 +9641,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // pick that up next time it starts. if (!attachedToProcess()) { ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration doesn't matter not running %s", this); - stopFreezingScreenLocked(false); forceNewConfig = false; return true; } @@ -9723,9 +9703,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } notifyDisplayCompatPolicyAboutConfigurationChange( mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig); - - stopFreezingScreenLocked(false); - return true; } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index dfb2a5fcf9fa..4b0177a36ebe 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1549,8 +1549,7 @@ class ActivityStarter { final ActivityRecord currentTop = startedActivityRootTask.topRunningActivity(); if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) { mRootWindowContainer.ensureVisibilityAndConfig( - currentTop, currentTop.getDisplayId(), - true /* markFrozenIfConfigChanged */, false /* deferResume */); + currentTop, currentTop.getDisplayId(), false /* deferResume */); } if (!mAvoidMoveToFront && mDoResume && mRootWindowContainer diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 73edb4bed6ca..869bcc0f9b8f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1053,6 +1053,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mWindowManager = wm; mRootWindowContainer = wm.mRoot; mWindowOrganizerController.mTransitionController.setWindowManager(wm); + mLifecycleManager.setWindowManager(wm); mTempConfig.setToDefaults(); mTempConfig.setLocales(LocaleList.getDefault()); mConfigurationSeq = mTempConfig.seq = 1; @@ -1274,7 +1275,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Nullable String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) { - final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions); assertPackageMatchesCallingUid(callingPackage); @@ -1315,7 +1315,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { .setActivityOptions(opts) .setUserId(userId) .execute(); - } @Override diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 4a479aa7351f..e59601c69cd8 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -843,7 +843,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // We don't want to perform a redundant launch of the same record while ensuring // configurations and trying to resume top activity of focused root task. mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(), - false /* markFrozenIfConfigChanged */, true /* deferResume */); + true /* deferResume */); } if (mKeyguardController.checkKeyguardVisibility(r) && r.allowMoveToFront()) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 92665af1075b..4929df8061b2 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static android.os.Process.SYSTEM_UID; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; @@ -49,7 +50,6 @@ import android.compat.annotation.EnabledAfter; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; -import android.os.Build; import android.os.Process; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -86,7 +86,7 @@ public class BackgroundActivityStartController { private static final int NO_PROCESS_UID = -1; /** If enabled the creator will not allow BAL on its behalf by default. */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE) private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR = 296478951; public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED = @@ -228,6 +228,7 @@ public class BackgroundActivityStartController { private final Intent mIntent; private final WindowProcessController mCallerApp; private final WindowProcessController mRealCallerApp; + private final boolean mIsCallForResult; private BalState(int callingUid, int callingPid, final String callingPackage, int realCallingUid, int realCallingPid, @@ -247,8 +248,10 @@ public class BackgroundActivityStartController { mOriginatingPendingIntent = originatingPendingIntent; mIntent = intent; mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid); - if (originatingPendingIntent == null // not a PendingIntent - || resultRecord != null // sent for result + mIsCallForResult = resultRecord != null; + if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature + && (originatingPendingIntent == null // not a PendingIntent + || mIsCallForResult) // sent for result ) { // grant BAL privileges unless explicitly opted out mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator = @@ -351,6 +354,19 @@ public class BackgroundActivityStartController { return name + "[debugOnly]"; } + /** @return valid targetSdk or <code>-1</code> */ + private int getTargetSdk(String packageName) { + if (packageName == null) { + return -1; + } + try { + PackageManager pm = mService.mContext.getPackageManager(); + return pm.getTargetSdkVersion(packageName); + } catch (Exception e) { + return -1; + } + } + private boolean hasRealCaller() { return mRealCallingUid != NO_PROCESS_UID; } @@ -368,6 +384,7 @@ public class BackgroundActivityStartController { StringBuilder sb = new StringBuilder(2048); sb.append("[callingPackage: ") .append(getDebugPackageName(mCallingPackage, mCallingUid)); + sb.append("; callingPackageTargetSdk: ").append(getTargetSdk(mCallingPackage)); sb.append("; callingUid: ").append(mCallingUid); sb.append("; callingPid: ").append(mCallingPid); sb.append("; appSwitchState: ").append(mAppSwitchState); @@ -387,10 +404,13 @@ public class BackgroundActivityStartController { .append(mBalAllowedByPiCreatorWithHardening); sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal); sb.append("; hasRealCaller: ").append(hasRealCaller()); + sb.append("; isCallForResult: ").append(mIsCallForResult); sb.append("; isPendingIntent: ").append(isPendingIntent()); if (hasRealCaller()) { sb.append("; realCallingPackage: ") .append(getDebugPackageName(mRealCallingPackage, mRealCallingUid)); + sb.append("; realCallingPackageTargetSdk: ") + .append(getTargetSdk(mRealCallingPackage)); sb.append("; realCallingUid: ").append(mRealCallingUid); sb.append("; realCallingPid: ").append(mRealCallingPid); sb.append("; realCallingUidHasAnyVisibleWindow: ") diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java index 8b282dd3e7a8..6b6388b10ce7 100644 --- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java +++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java @@ -22,7 +22,13 @@ import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; /** * Class that is able to combine multiple client lifecycle transition requests and/or callbacks, @@ -31,8 +37,18 @@ import android.os.RemoteException; * @see ClientTransaction */ class ClientLifecycleManager { - // TODO(lifecycler): Implement building transactions or global transaction. - // TODO(lifecycler): Use object pools for transactions and transaction items. + + private static final String TAG = "ClientLifecycleManager"; + + /** Mapping from client process binder to its pending transaction. */ + @VisibleForTesting + final ArrayMap<IBinder, ClientTransaction> mPendingTransactions = new ArrayMap<>(); + + private WindowManagerService mWms; + + void setWindowManager(@NonNull WindowManagerService wms) { + mWms = wms; + } /** * Schedules a transaction, which may consist of multiple callbacks and a lifecycle request. @@ -82,14 +98,24 @@ class ClientLifecycleManager { */ void scheduleTransactionItem(@NonNull IApplicationThread client, @NonNull ClientTransactionItem transactionItem) throws RemoteException { - // TODO(b/260873529): queue the transaction items. - final ClientTransaction clientTransaction = ClientTransaction.obtain(client); - if (transactionItem.isActivityLifecycleItem()) { - clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem); + // The behavior is different depending on the flag. + // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to + // dispatch all pending transactions at once. + if (Flags.bundleClientTransactionFlag()) { + final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client); + clientTransaction.addTransactionItem(transactionItem); + + onClientTransactionItemScheduledLocked(clientTransaction); } else { - clientTransaction.addCallback(transactionItem); + // TODO(b/260873529): cleanup after launch. + final ClientTransaction clientTransaction = ClientTransaction.obtain(client); + if (transactionItem.isActivityLifecycleItem()) { + clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem); + } else { + clientTransaction.addCallback(transactionItem); + } + scheduleTransaction(clientTransaction); } - scheduleTransaction(clientTransaction); } /** @@ -100,10 +126,67 @@ class ClientLifecycleManager { void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client, @NonNull ClientTransactionItem transactionItem, @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException { - // TODO(b/260873529): replace with #scheduleTransactionItem after launch for cleanup. - final ClientTransaction clientTransaction = ClientTransaction.obtain(client); - clientTransaction.addCallback(transactionItem); - clientTransaction.setLifecycleStateRequest(lifecycleItem); + // The behavior is different depending on the flag. + // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to + // dispatch all pending transactions at once. + if (Flags.bundleClientTransactionFlag()) { + final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client); + clientTransaction.addTransactionItem(transactionItem); + clientTransaction.addTransactionItem(lifecycleItem); + + onClientTransactionItemScheduledLocked(clientTransaction); + } else { + // TODO(b/260873529): cleanup after launch. + final ClientTransaction clientTransaction = ClientTransaction.obtain(client); + clientTransaction.addCallback(transactionItem); + clientTransaction.setLifecycleStateRequest(lifecycleItem); + scheduleTransaction(clientTransaction); + } + } + + /** Executes all the pending transactions. */ + void dispatchPendingTransactions() { + final int size = mPendingTransactions.size(); + for (int i = 0; i < size; i++) { + final ClientTransaction transaction = mPendingTransactions.valueAt(i); + try { + scheduleTransaction(transaction); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to deliver transaction for " + transaction.getClient()); + } + } + mPendingTransactions.clear(); + } + + @NonNull + private ClientTransaction getOrCreatePendingTransaction(@NonNull IApplicationThread client) { + final IBinder clientBinder = client.asBinder(); + final ClientTransaction pendingTransaction = mPendingTransactions.get(clientBinder); + if (pendingTransaction != null) { + return pendingTransaction; + } + + // Create new transaction if there is no existing. + final ClientTransaction transaction = ClientTransaction.obtain(client); + mPendingTransactions.put(clientBinder, transaction); + return transaction; + } + + /** Must only be called with WM lock. */ + private void onClientTransactionItemScheduledLocked( + @NonNull ClientTransaction clientTransaction) throws RemoteException { + // TODO(b/260873529): make sure WindowSurfacePlacer#requestTraversal is called before + // ClientTransaction scheduled when needed. + + if (mWms != null && (mWms.mWindowPlacerLocked.isInLayout() + || mWms.mWindowPlacerLocked.isTraversalScheduled())) { + // The pending transactions will be dispatched when + // RootWindowContainer#performSurfacePlacementNoTrace. + return; + } + + // Dispatch the pending transaction immediately. + mPendingTransactions.remove(clientTransaction.getClient().asBinder()); scheduleTransaction(clientTransaction); } } diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java new file mode 100644 index 000000000000..975fdc0ade5d --- /dev/null +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.WindowManager.TRANSIT_CHANGE; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS; +import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; +import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS; +import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.view.DisplayInfo; +import android.window.DisplayAreaInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater; + +import java.util.Arrays; +import java.util.Objects; + +/** + * A DisplayUpdater that could defer and queue display updates coming from DisplayManager to + * WindowManager. It allows to defer pending display updates if WindowManager is currently not + * ready to apply them. + * For example, this might happen if there is a Shell transition running and physical display + * changed. We can't immediately apply the display updates because we want to start a separate + * display change transition. In this case, we will queue all display updates until the current + * transition's collection finishes and then apply them afterwards. + */ +public class DeferredDisplayUpdater implements DisplayUpdater { + + /** + * List of fields that could be deferred before applying to DisplayContent. + * This should be kept in sync with {@link DeferredDisplayUpdater#calculateDisplayInfoDiff} + */ + @VisibleForTesting + static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> { + // Treat unique id and address change as WM-specific display change as we re-query display + // settings and parameters based on it which could cause window changes + out.uniqueId = override.uniqueId; + out.address = override.address; + + // Also apply WM-override fields, since they might produce differences in window hierarchy + WM_OVERRIDE_FIELDS.setFields(out, override); + }; + + private final DisplayContent mDisplayContent; + + @NonNull + private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo(); + + /** + * The last known display parameters from DisplayManager, some WM-specific fields in this object + * might not be applied to the DisplayContent yet + */ + @Nullable + private DisplayInfo mLastDisplayInfo; + + /** + * The last DisplayInfo that was applied to DisplayContent, only WM-specific parameters must be + * used from this object. This object is used to store old values of DisplayInfo while these + * fields are pending to be applied to DisplayContent. + */ + @Nullable + private DisplayInfo mLastWmDisplayInfo; + + @NonNull + private final DisplayInfo mOutputDisplayInfo = new DisplayInfo(); + + public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) { + mDisplayContent = displayContent; + mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); + } + + /** + * Reads the latest display parameters from the display manager and returns them in a callback. + * If there are pending display updates, it will wait for them to finish first and only then it + * will call the callback with the latest display parameters. + * + * @param finishCallback is called when all pending display updates are finished + */ + @Override + public void updateDisplayInfo(@NonNull Runnable finishCallback) { + // Get the latest display parameters from the DisplayManager + final DisplayInfo displayInfo = getCurrentDisplayInfo(); + + final int displayInfoDiff = calculateDisplayInfoDiff(mLastDisplayInfo, displayInfo); + final boolean physicalDisplayUpdated = isPhysicalDisplayUpdated(mLastDisplayInfo, + displayInfo); + + mLastDisplayInfo = displayInfo; + + // Apply whole display info immediately as is if either: + // * it is the first display update + // * shell transitions are disabled or temporary unavailable + if (displayInfoDiff == DIFF_EVERYTHING + || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: applying DisplayInfo immediately"); + + mLastWmDisplayInfo = displayInfo; + applyLatestDisplayInfo(); + finishCallback.run(); + return; + } + + // If there are non WM-specific display info changes, apply only these fields immediately + if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: partially applying DisplayInfo immediately"); + applyLatestDisplayInfo(); + } + + // If there are WM-specific display info changes, apply them through a Shell transition + if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: deferring DisplayInfo update"); + + requestDisplayChangeTransition(physicalDisplayUpdated, () -> { + // Apply deferrable fields to DisplayContent only when the transition + // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo + mLastWmDisplayInfo = displayInfo; + applyLatestDisplayInfo(); + finishCallback.run(); + }); + } else { + // There are no WM-specific updates, so we can immediately notify that all display + // info changes are applied + finishCallback.run(); + } + } + + /** + * Requests a display change Shell transition + * + * @param physicalDisplayUpdated if true also starts remote display change + * @param onStartCollect called when the Shell transition starts collecting + */ + private void requestDisplayChangeTransition(boolean physicalDisplayUpdated, + @NonNull Runnable onStartCollect) { + + final Transition transition = new Transition(TRANSIT_CHANGE, /* flags= */ 0, + mDisplayContent.mTransitionController, + mDisplayContent.mTransitionController.mSyncEngine); + + mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); + + mDisplayContent.mTransitionController.startCollectOrQueue(transition, deferred -> { + final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth, + mDisplayContent.mInitialDisplayHeight); + final int fromRotation = mDisplayContent.getRotation(); + + onStartCollect.run(); + + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: applied DisplayInfo after deferring"); + + if (physicalDisplayUpdated) { + onDisplayUpdated(transition, fromRotation, startBounds); + } else { + transition.setAllReady(); + } + }); + } + + /** + * Applies current DisplayInfo to DisplayContent, DisplayContent is merged from two parts: + * - non-deferrable fields are set from the most recent values received from DisplayManager + * (uses {@link mLastDisplayInfo} field) + * - deferrable fields are set from the latest values that we could apply to WM + * (uses {@link mLastWmDisplayInfo} field) + */ + private void applyLatestDisplayInfo() { + copyDisplayInfoFields(mOutputDisplayInfo, /* base= */ mLastDisplayInfo, + /* override= */ mLastWmDisplayInfo, /* fields= */ DEFERRABLE_FIELDS); + mDisplayContent.onDisplayInfoUpdated(mOutputDisplayInfo); + } + + @NonNull + private DisplayInfo getCurrentDisplayInfo() { + mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo( + mDisplayContent.mDisplayId, mNonOverrideDisplayInfo); + return new DisplayInfo(mNonOverrideDisplayInfo); + } + + /** + * Called when physical display is updated, this could happen e.g. on foldable + * devices when the physical underlying display is replaced. This method should be called + * when the new display info is already applied to the WM hierarchy. + * + * @param fromRotation rotation before the display change + * @param startBounds display bounds before the display change + */ + private void onDisplayUpdated(@NonNull Transition transition, int fromRotation, + @NonNull Rect startBounds) { + final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth, + mDisplayContent.mInitialDisplayHeight); + final int toRotation = mDisplayContent.getRotation(); + + final TransitionRequestInfo.DisplayChange displayChange = + new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId()); + displayChange.setStartAbsBounds(startBounds); + displayChange.setEndAbsBounds(endBounds); + displayChange.setStartRotation(fromRotation); + displayChange.setEndRotation(toRotation); + displayChange.setPhysicalDisplayChanged(true); + + mDisplayContent.mTransitionController.requestStartTransition(transition, + /* startTask= */ null, /* remoteTransition= */ null, displayChange); + + final DisplayAreaInfo newDisplayAreaInfo = mDisplayContent.getDisplayAreaInfo(); + + final boolean startedRemoteChange = mDisplayContent.mRemoteDisplayChangeController + .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo, + transaction -> finishDisplayUpdate(transaction, transition)); + + if (!startedRemoteChange) { + finishDisplayUpdate(/* wct= */ null, transition); + } + } + + private void finishDisplayUpdate(@Nullable WindowContainerTransaction wct, + @NonNull Transition transition) { + if (wct != null) { + mDisplayContent.mAtmService.mWindowOrganizerController.applyTransaction( + wct); + } + transition.setAllReady(); + } + + private boolean isPhysicalDisplayUpdated(@Nullable DisplayInfo first, + @Nullable DisplayInfo second) { + if (first == null || second == null) return true; + return !Objects.equals(first.uniqueId, second.uniqueId); + } + + /** + * Diff result: fields are the same + */ + static final int DIFF_NONE = 0; + + /** + * Diff result: fields that could be deferred in WM are different + */ + static final int DIFF_WM_DEFERRABLE = 1 << 0; + + /** + * Diff result: fields that could not be deferred in WM are different + */ + static final int DIFF_NOT_WM_DEFERRABLE = 1 << 1; + + /** + * Diff result: everything is different + */ + static final int DIFF_EVERYTHING = 0XFFFFFFFF; + + @VisibleForTesting + static int calculateDisplayInfoDiff(@Nullable DisplayInfo first, @Nullable DisplayInfo second) { + int diff = DIFF_NONE; + + if (Objects.equals(first, second)) return diff; + if (first == null || second == null) return DIFF_EVERYTHING; + + if (first.layerStack != second.layerStack + || first.flags != second.flags + || first.type != second.type + || first.displayId != second.displayId + || first.displayGroupId != second.displayGroupId + || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo) + || first.modeId != second.modeId + || first.renderFrameRate != second.renderFrameRate + || first.defaultModeId != second.defaultModeId + || first.userPreferredModeId != second.userPreferredModeId + || !Arrays.equals(first.supportedModes, second.supportedModes) + || first.colorMode != second.colorMode + || !Arrays.equals(first.supportedColorModes, second.supportedColorModes) + || !Objects.equals(first.hdrCapabilities, second.hdrCapabilities) + || !Arrays.equals(first.userDisabledHdrTypes, second.userDisabledHdrTypes) + || first.minimalPostProcessingSupported != second.minimalPostProcessingSupported + || first.appVsyncOffsetNanos != second.appVsyncOffsetNanos + || first.presentationDeadlineNanos != second.presentationDeadlineNanos + || first.state != second.state + || first.committedState != second.committedState + || first.ownerUid != second.ownerUid + || !Objects.equals(first.ownerPackageName, second.ownerPackageName) + || first.removeMode != second.removeMode + || first.getRefreshRate() != second.getRefreshRate() + || first.brightnessMinimum != second.brightnessMinimum + || first.brightnessMaximum != second.brightnessMaximum + || first.brightnessDefault != second.brightnessDefault + || first.installOrientation != second.installOrientation + || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate) + || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio) + || !first.thermalRefreshRateThrottling.contentEquals( + second.thermalRefreshRateThrottling) + || !Objects.equals(first.thermalBrightnessThrottlingDataId, + second.thermalBrightnessThrottlingDataId)) { + diff |= DIFF_NOT_WM_DEFERRABLE; + } + + if (first.appWidth != second.appWidth + || first.appHeight != second.appHeight + || first.smallestNominalAppWidth != second.smallestNominalAppWidth + || first.smallestNominalAppHeight != second.smallestNominalAppHeight + || first.largestNominalAppWidth != second.largestNominalAppWidth + || first.largestNominalAppHeight != second.largestNominalAppHeight + || first.logicalWidth != second.logicalWidth + || first.logicalHeight != second.logicalHeight + || first.physicalXDpi != second.physicalXDpi + || first.physicalYDpi != second.physicalYDpi + || first.rotation != second.rotation + || !Objects.equals(first.displayCutout, second.displayCutout) + || first.logicalDensityDpi != second.logicalDensityDpi + || !Objects.equals(first.roundedCorners, second.roundedCorners) + || !Objects.equals(first.displayShape, second.displayShape) + || !Objects.equals(first.uniqueId, second.uniqueId) + || !Objects.equals(first.address, second.address) + ) { + diff |= DIFF_WM_DEFERRABLE; + } + + return diff; + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a840973a941e..ae10ce3690aa 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -276,6 +276,7 @@ import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import static com.android.window.flags.Flags.deferDisplayUpdates; /** * Utility class for keeping track of the WindowStates and other pertinent contents of a @@ -545,10 +546,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean isDefaultDisplay; /** Detect user tapping outside of current focused task bounds .*/ + // TODO(b/315321016): Remove once pointer event detection is removed from WM. @VisibleForTesting final TaskTapPointerEventListener mTapDetector; /** Detect user tapping outside of current focused root task bounds .*/ + // TODO(b/315321016): Remove once pointer event detection is removed from WM. private Region mTouchExcludeRegion = new Region(); /** Save allocating when calculating rects */ @@ -1158,7 +1161,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWallpaperController.resetLargestDisplay(display); display.getDisplayInfo(mDisplayInfo); display.getMetrics(mDisplayMetrics); - mDisplayUpdater = new ImmediateDisplayUpdater(this); + if (deferDisplayUpdates()) { + mDisplayUpdater = new DeferredDisplayUpdater(this); + } else { + mDisplayUpdater = new ImmediateDisplayUpdater(this); + } mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp * mDisplayMetrics.densityDpi / DENSITY_DEFAULT; isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; @@ -1189,12 +1196,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp "PointerEventDispatcher" + mDisplayId, mDisplayId); mPointerEventDispatcher = new PointerEventDispatcher(inputChannel); - // Tap Listeners are supported for: - // 1. All physical displays (multi-display). - // 2. VirtualDisplays on VR, AA (and everything else). - mTapDetector = new TaskTapPointerEventListener(mWmService, this); - registerPointerEventListener(mTapDetector); - registerPointerEventListener(mWmService.mMousePositionTracker); + if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) { + mTapDetector = null; + } else { + // Tap Listeners are supported for: + // 1. All physical displays (multi-display). + // 2. VirtualDisplays on VR, AA (and everything else). + mTapDetector = new TaskTapPointerEventListener(mWmService, this); + registerPointerEventListener(mTapDetector); + } + if (mWmService.mMousePositionTracker != null) { + registerPointerEventListener(mWmService.mMousePositionTracker); + } if (mWmService.mAtmService.getRecentTasks() != null) { registerPointerEventListener( mWmService.mAtmService.getRecentTasks().getInputListener()); @@ -1636,12 +1649,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mWindowPlacerLocked.performSurfacePlacement(); } - void sendNewConfiguration() { + /** Returns {@code true} if the display configuration is changed. */ + boolean sendNewConfiguration() { if (!isReady()) { - return; + return false; } if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) { - return; + return false; } final Transition.ReadyCondition displayConfig = mTransitionController.isCollecting() @@ -1656,7 +1670,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp displayConfig.meet(); } if (configUpdated) { - return; + return true; } // The display configuration doesn't change. If there is a launching transformed app, that @@ -1674,6 +1688,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp setLayoutNeeded(); mWmService.mWindowPlacerLocked.performSurfacePlacement(); } + return false; } @Override @@ -1695,7 +1710,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final ActivityRecord activityRecord = (ActivityRecord) requestingContainer; final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord, false /* deferResume */, null /* result */); - activityRecord.frozenBeforeDestroy = true; if (!kept) { mRootWindowContainer.resumeFocusedTasksTopActivities(); } @@ -3260,6 +3274,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void updateTouchExcludeRegion() { + if (mTapDetector == null) { + // The touch exclude region is used to detect the region outside of the focused task + // so that the tap detector can detect outside touches. Don't calculate the exclude + // region when the tap detector is disabled. + return; + } final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null); if (focusedTask == null) { mTouchExcludeRegion.setEmpty(); @@ -3298,6 +3318,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) { + if (mTapDetector == null) { + // The touch exclude region is used to detect the region outside of the focused task + // so that the tap detector can detect outside touches. Don't calculate the exclude + // region when the tap detector is disabled. + } final ActivityRecord topVisibleActivity = task.getTopVisibleActivity(); if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) { @@ -5124,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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index b862d7c28b52..460a68f48ff6 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -39,6 +39,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCRE import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; @@ -272,6 +273,8 @@ public class DisplayPolicy { private @InsetsType int mForciblyShownTypes; + private boolean mImeInsetsConsumed; + private boolean mIsImmersiveMode; // The windows we were told about in focusChanged. @@ -1420,6 +1423,7 @@ public class DisplayPolicy { mShowingDream = false; mIsFreeformWindowOverlappingWithNavBar = false; mForciblyShownTypes = 0; + mImeInsetsConsumed = false; } /** @@ -1481,6 +1485,17 @@ public class DisplayPolicy { mForciblyShownTypes |= win.mAttrs.forciblyShownTypes; } + if (win.mImeInsetsConsumed != mImeInsetsConsumed) { + win.mImeInsetsConsumed = mImeInsetsConsumed; + final WindowState imeWin = mDisplayContent.mInputMethodWindow; + if (win.isReadyToDispatchInsetsState() && imeWin != null && imeWin.isVisible()) { + win.notifyInsetsChanged(); + } + } + if ((attrs.privateFlags & PRIVATE_FLAG_CONSUME_IME_INSETS) != 0 && win.isVisible()) { + mImeInsetsConsumed = true; + } + if (!affectsSystemUi) { return; } @@ -2828,6 +2843,7 @@ public class DisplayPolicy { } } pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen); + pw.print(prefix); pw.print("mImeInsetsConsumed="); pw.println(mImeInsetsConsumed); pw.print(prefix); pw.print("mForceShowNavigationBarEnabled="); pw.print(mForceShowNavigationBarEnabled); pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn); diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 4a3d0c142e1d..32d60c5f52e6 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static com.android.input.flags.Flags.enablePointerChoreographer; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -23,6 +24,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.content.ClipData; import android.content.Context; +import android.hardware.input.InputManagerGlobal; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -31,6 +33,8 @@ import android.os.Message; import android.util.Slog; import android.view.Display; import android.view.IWindow; +import android.view.InputDevice; +import android.view.PointerIcon; import android.view.SurfaceControl; import android.view.View; import android.view.accessibility.AccessibilityManager; @@ -97,8 +101,8 @@ class DragDropController { } IBinder performDrag(int callerPid, int callerUid, IWindow window, int flags, - SurfaceControl surface, int touchSource, float touchX, float touchY, - float thumbCenterX, float thumbCenterY, ClipData data) { + SurfaceControl surface, int touchSource, int touchDeviceId, int touchPointerId, + float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) { if (DEBUG_DRAG) { Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" + Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + "," @@ -208,7 +212,17 @@ class DragDropController { final SurfaceControl surfaceControl = mDragState.mSurfaceControl; mDragState.broadcastDragStartedLocked(touchX, touchY); - mDragState.overridePointerIconLocked(touchSource); + if (enablePointerChoreographer()) { + if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + InputManagerGlobal.getInstance().setPointerIcon( + PointerIcon.getSystemIcon( + mService.mContext, PointerIcon.TYPE_GRABBING), + mDragState.mDisplayContent.getDisplayId(), touchDeviceId, + touchPointerId, mDragState.getInputToken()); + } + } else { + mDragState.overridePointerIconLocked(touchSource); + } // remember the thumb offsets for later mDragState.mThumbOffsetX = thumbCenterX; mDragState.mThumbOffsetY = thumbCenterY; diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index a888f8467b3a..adbe3bc1d6b3 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -416,6 +416,13 @@ class DragState { return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle; } + IBinder getInputToken() { + if (mInputInterceptor == null || mInputInterceptor.mClientChannel == null) { + return null; + } + return mInputInterceptor.mClientChannel.getToken(); + } + /** * @param display The Display that the window being dragged is on. */ diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index c089d107d07d..781567990235 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -391,13 +391,26 @@ class InsetsPolicy { if (originalImeSource != null) { final boolean imeVisibility = w.isRequestedVisible(Type.ime()); - final InsetsState state = copyState ? new InsetsState(originalState) + final InsetsState state = copyState + ? new InsetsState(originalState) : originalState; final InsetsSource imeSource = new InsetsSource(originalImeSource); imeSource.setVisible(imeVisibility); state.addSource(imeSource); return state; } + } else if (w.mImeInsetsConsumed) { + // Set the IME source (if there is one) to be invisible if it has been consumed. + final InsetsSource originalImeSource = originalState.peekSource(ID_IME); + if (originalImeSource != null && originalImeSource.isVisible()) { + final InsetsState state = copyState + ? new InsetsState(originalState) + : originalState; + final InsetsSource imeSource = new InsetsSource(originalImeSource); + imeSource.setVisible(false); + state.addSource(imeSource); + return state; + } } return originalState; } diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java index 9cbc1bdcbeeb..4c73a415d3f3 100644 --- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java +++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java @@ -84,10 +84,10 @@ class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implement } @Override - public void onActivityLaunched(long id, ComponentName name, int temperature) { + public void onActivityLaunched(long id, ComponentName name, int temperature, int userId) { mHandler.sendMessage(PooledLambda.obtainMessage( LaunchObserverRegistryImpl::handleOnActivityLaunched, - this, id, name, temperature)); + this, id, name, temperature, userId)); } @Override @@ -97,10 +97,11 @@ class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implement } @Override - public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs) { + public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs, + int launchMode) { mHandler.sendMessage(PooledLambda.obtainMessage( LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, - this, id, name, timestampNs)); + this, id, name, timestampNs, launchMode)); } @Override @@ -137,10 +138,10 @@ class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implement } private void handleOnActivityLaunched(long id, ComponentName name, - @Temperature int temperature) { + @Temperature int temperature, int userId) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - mList.get(i).onActivityLaunched(id, name, temperature); + mList.get(i).onActivityLaunched(id, name, temperature, userId); } } @@ -151,10 +152,11 @@ class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implement } } - private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs) { + private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs, + int launchMode) { // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee. for (int i = 0; i < mList.size(); i++) { - mList.get(i).onActivityLaunchFinished(id, name, timestampNs); + mList.get(i).onActivityLaunchFinished(id, name, timestampNs, launchMode); } } diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 23c135a7b83a..03574029c061 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -26,6 +26,7 @@ import android.view.Display; import android.view.Display.Mode; import android.view.DisplayInfo; import android.view.Surface; +import android.view.SurfaceControl; import android.view.SurfaceControl.RefreshRateRange; import java.util.HashMap; @@ -191,26 +192,35 @@ class RefreshRatePolicy { public static class FrameRateVote { float mRefreshRate; @Surface.FrameRateCompatibility int mCompatibility; + @SurfaceControl.FrameRateSelectionStrategy int mSelectionStrategy; - FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) { - update(refreshRate, compatibility); + + + FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility, + @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) { + update(refreshRate, compatibility, selectionStrategy); } FrameRateVote() { reset(); } - boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) { - if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) { + boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility, + @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) { + if (!refreshRateEquals(refreshRate) + || mCompatibility != compatibility + || mSelectionStrategy != selectionStrategy) { mRefreshRate = refreshRate; mCompatibility = compatibility; + mSelectionStrategy = selectionStrategy; return true; } return false; } boolean reset() { - return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE); } @Override @@ -221,17 +231,20 @@ class RefreshRatePolicy { FrameRateVote other = (FrameRateVote) o; return refreshRateEquals(other.mRefreshRate) - && mCompatibility == other.mCompatibility; + && mCompatibility == other.mCompatibility + && mSelectionStrategy == other.mSelectionStrategy; } @Override public int hashCode() { - return Objects.hash(mRefreshRate, mCompatibility); + return Objects.hash(mRefreshRate, mCompatibility, mSelectionStrategy); + } @Override public String toString() { - return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility; + return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility + + ", mSelectionStrategy=" + mSelectionStrategy; } private boolean refreshRateEquals(float refreshRate) { @@ -265,7 +278,8 @@ class RefreshRatePolicy { for (Display.Mode mode : mDisplayInfo.supportedModes) { if (preferredModeId == mode.getModeId()) { return w.mFrameRateVote.update(mode.getRefreshRate(), - Surface.FRAME_RATE_COMPATIBILITY_EXACT); + Surface.FRAME_RATE_COMPATIBILITY_EXACT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } } } @@ -273,7 +287,8 @@ class RefreshRatePolicy { if (w.mAttrs.preferredRefreshRate > 0) { return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate, - Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } // If the app didn't set a preferred mode id or refresh rate, but it is part of the deny @@ -282,7 +297,8 @@ class RefreshRatePolicy { final String packageName = w.getOwningPackage(); if (mHighRefreshRateDenylist.isDenylisted(packageName)) { return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(), - Surface.FRAME_RATE_COMPATIBILITY_EXACT); + Surface.FRAME_RATE_COMPATIBILITY_EXACT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 522e7d205a00..9a75dae3569e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -842,6 +842,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> handleResizingWindows(); clearFrameChangingWindows(); + // Called after #handleResizingWindows to include WindowStateResizeItem if any. + mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions(); + if (mWmService.mDisplayFrozen) { ProtoLog.v(WM_DEBUG_ORIENTATION, "With display frozen, orientationChangeComplete=%b", @@ -1741,14 +1744,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * @param starting The currently starting activity or {@code null} if there is * none. * @param displayId The id of the display where operation is executed. - * @param markFrozenIfConfigChanged Whether to set {@link ActivityRecord#frozenBeforeDestroy} to - * {@code true} if config changed. * @param deferResume Whether to defer resume while updating config. * @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched * because of configuration update. */ - boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, - boolean markFrozenIfConfigChanged, boolean deferResume) { + boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, boolean deferResume) { // First ensure visibility without updating the config just yet. We need this to know what // activities are affecting configuration now. // Passing null here for 'starting' param value, so that visibility of actual starting @@ -1774,9 +1774,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (starting != null) { starting.reportDescendantOrientationChangeIfNeeded(); } - if (starting != null && markFrozenIfConfigChanged && config != null) { - starting.frozenBeforeDestroy = true; - } if (displayContent != null) { // Update the configuration of the activities on the display. diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 56f9aa4c6361..57939bc4f348 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -340,7 +340,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource, - float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) { + int touchDeviceId, int touchPointerId, float touchX, float touchY, float thumbCenterX, + float thumbCenterY, ClipData data) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); // Validate and resolve ClipDescription data before clearing the calling identity @@ -349,7 +350,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final long ident = Binder.clearCallingIdentity(); try { return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource, - touchX, touchY, thumbCenterX, thumbCenterY, data); + touchDeviceId, touchPointerId, touchX, touchY, thumbCenterX, thumbCenterY, + data); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 5f082124dbcb..671acfc697e4 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5815,8 +5815,7 @@ class Task extends TaskFragment { } mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */, - mDisplayContent.mDisplayId, false /* markFrozenIfConfigChanged */, - false /* deferResume */); + mDisplayContent.mDisplayId, false /* deferResume */); } finally { if (mTransitionController.isShellTransitionsEnabled()) { mAtmService.continueWindowLayout(); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 39b4480a7da0..5d019122d52e 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -73,6 +73,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; +import android.app.IApplicationThread; import android.app.ResultInfo; import android.app.WindowConfiguration; import android.app.servertransaction.ActivityResultItem; @@ -103,6 +104,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.am.HostingRecord; import com.android.server.pm.pkg.AndroidPackage; +import com.android.window.flags.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -1485,7 +1487,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // TODO: Remove this once visibilities are set correctly immediately when // starting an activity. notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(), - true /* markFrozenIfConfigChanged */, false /* deferResume */); + false /* deferResume */); } if (notUpdated) { @@ -1509,23 +1511,38 @@ class TaskFragment extends WindowContainer<WindowContainer> { } try { - final ClientTransaction transaction = ClientTransaction.obtain( - next.app.getThread()); + final IApplicationThread appThread = next.app.getThread(); + final ClientTransaction transaction = Flags.bundleClientTransactionFlag() + ? null + : ClientTransaction.obtain(appThread); // Deliver all pending results. - ArrayList<ResultInfo> a = next.results; + final ArrayList<ResultInfo> a = next.results; if (a != null) { final int size = a.size(); if (!next.finishing && size > 0) { if (DEBUG_RESULTS) { Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a); } - transaction.addCallback(ActivityResultItem.obtain(next.token, a)); + final ActivityResultItem activityResultItem = ActivityResultItem.obtain( + next.token, a); + if (transaction == null) { + mAtmService.getLifecycleManager().scheduleTransactionItem( + appThread, activityResultItem); + } else { + transaction.addCallback(activityResultItem); + } } } if (next.newIntents != null) { - transaction.addCallback( - NewIntentItem.obtain(next.token, next.newIntents, true /* resume */)); + final NewIntentItem newIntentItem = NewIntentItem.obtain( + next.token, next.newIntents, true /* resume */); + if (transaction == null) { + mAtmService.getLifecycleManager().scheduleTransactionItem( + appThread, newIntentItem); + } else { + transaction.addCallback(newIntentItem); + } } // Well the app will no longer be stopped. @@ -1539,10 +1556,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { final int topProcessState = mAtmService.mTopProcessState; next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState); next.abortAndClearOptionsAnimation(); - transaction.setLifecycleStateRequest( - ResumeActivityItem.obtain(next.token, topProcessState, - dc.isNextTransitionForward(), next.shouldSendCompatFakeFocus())); - mAtmService.getLifecycleManager().scheduleTransaction(transaction); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain( + next.token, topProcessState, dc.isNextTransitionForward(), + next.shouldSendCompatFakeFocus()); + if (transaction == null) { + mAtmService.getLifecycleManager().scheduleTransactionItem( + appThread, resumeActivityItem); + } else { + transaction.setLifecycleStateRequest(resumeActivityItem); + mAtmService.getLifecycleManager().scheduleTransaction(transaction); + } ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next); } catch (Exception e) { @@ -1856,7 +1879,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // In that case go ahead and remove the freeze this activity has on the screen // since it is no longer visible. if (prev != null) { - prev.stopFreezingScreenLocked(true /*force*/); + prev.stopFreezingScreen(true /* unfreezeNow */, true /* force */); } mPausingActivity = null; } diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java index 7d22b744ad5f..ac244c7b048e 100644 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java @@ -45,6 +45,10 @@ public class TaskTapPointerEventListener implements PointerEventListener { public TaskTapPointerEventListener(WindowManagerService service, DisplayContent displayContent) { + // TODO(b/315321016): Remove this class when the flag rollout is complete. + if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) { + throw new IllegalStateException("TaskTapPointerEventListener should not be used!"); + } mService = service; mDisplayContent = displayContent; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 76c4a0ee438b..f020bfa8cbc7 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2868,8 +2868,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final WindowContainer<?> wc = mParticipants.valueAt(i); final DisplayContent dc = wc.asDisplayContent(); if (dc == null || !mChanges.get(dc).hasChanged()) continue; - final int originalSeq = dc.getConfiguration().seq; - dc.sendNewConfiguration(); + final boolean changed = dc.sendNewConfiguration(); // Set to ready if no other change controls the ready state. But if there is, such as // if an activity is pausing, it will call setReady(ar, false) and wait for the next // resumed activity. Then do not set to ready because the transition only contains @@ -2877,7 +2876,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (!mReadyTrackerOld.mUsed) { setReady(dc, true); } - if (originalSeq == dc.getConfiguration().seq) continue; + if (!changed) continue; // If the update is deferred, sendNewConfiguration won't deliver new configuration to // clients, then it is the caller's responsibility to deliver the changes. if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) { diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java new file mode 100644 index 000000000000..1688a1a91114 --- /dev/null +++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java @@ -0,0 +1,448 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.graphics.Matrix.MSCALE_X; +import static android.graphics.Matrix.MSCALE_Y; +import static android.graphics.Matrix.MSKEW_X; +import static android.graphics.Matrix.MSKEW_Y; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IntArray; +import android.util.Pair; +import android.util.Size; +import android.view.InputWindowHandle; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; +import android.window.WindowInfosListener; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.wm.utils.RegionUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Optional; + +/** + * Class to handle TrustedPresentationListener registrations in a thread safe manner. This class + * also takes care of cleaning up listeners when the remote process dies. + */ +public class TrustedPresentationListenerController { + + // Should only be accessed by the posting to the handler + private class Listeners { + private final class ListenerDeathRecipient implements IBinder.DeathRecipient { + IBinder mListenerBinder; + int mInstances; + + ListenerDeathRecipient(IBinder listenerBinder) { + mListenerBinder = listenerBinder; + mInstances = 0; + try { + mListenerBinder.linkToDeath(this, 0); + } catch (RemoteException ignore) { + } + } + + void addInstance() { + mInstances++; + } + + // return true if there are no instances alive + boolean removeInstance() { + mInstances--; + if (mInstances > 0) { + return false; + } + mListenerBinder.unlinkToDeath(this, 0); + return true; + } + + public void binderDied() { + mHandler.post(() -> { + mUniqueListeners.remove(mListenerBinder); + removeListeners(mListenerBinder, Optional.empty()); + }); + } + } + + // tracks binder deaths for cleanup + ArrayMap<IBinder, ListenerDeathRecipient> mUniqueListeners = new ArrayMap<>(); + ArrayMap<IBinder /*window*/, ArrayList<TrustedPresentationInfo>> mWindowToListeners = + new ArrayMap<>(); + + void register(IBinder window, ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + var listenersForWindow = mWindowToListeners.computeIfAbsent(window, + iBinder -> new ArrayList<>()); + listenersForWindow.add(new TrustedPresentationInfo(thresholds, id, listener)); + + // register death listener + var listenerBinder = listener.asBinder(); + var deathRecipient = mUniqueListeners.computeIfAbsent(listenerBinder, + ListenerDeathRecipient::new); + deathRecipient.addInstance(); + } + + void unregister(ITrustedPresentationListener trustedPresentationListener, int id) { + var listenerBinder = trustedPresentationListener.asBinder(); + var deathRecipient = mUniqueListeners.get(listenerBinder); + if (deathRecipient == null) { + ProtoLog.e(WM_DEBUG_TPL, "unregister failed, couldn't find" + + " deathRecipient for %s with id=%d", trustedPresentationListener, id); + return; + } + + if (deathRecipient.removeInstance()) { + mUniqueListeners.remove(listenerBinder); + } + removeListeners(listenerBinder, Optional.of(id)); + } + + boolean isEmpty() { + return mWindowToListeners.isEmpty(); + } + + ArrayList<TrustedPresentationInfo> get(IBinder windowToken) { + return mWindowToListeners.get(windowToken); + } + + private void removeListeners(IBinder listenerBinder, Optional<Integer> id) { + for (int i = mWindowToListeners.size() - 1; i >= 0; i--) { + var listeners = mWindowToListeners.valueAt(i); + for (int j = listeners.size() - 1; j >= 0; j--) { + var listener = listeners.get(j); + if (listener.mListener.asBinder() == listenerBinder && (id.isEmpty() + || listener.mId == id.get())) { + listeners.remove(j); + } + } + if (listeners.isEmpty()) { + mWindowToListeners.removeAt(i); + } + } + } + } + + private final Object mHandlerThreadLock = new Object(); + private HandlerThread mHandlerThread; + private Handler mHandler; + + private WindowInfosListener mWindowInfosListener; + + Listeners mRegisteredListeners = new Listeners(); + + private InputWindowHandle[] mLastWindowHandles; + + private final Object mIgnoredWindowTokensLock = new Object(); + + private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>(); + + private void startHandlerThreadIfNeeded() { + synchronized (mHandlerThreadLock) { + if (mHandler == null) { + mHandlerThread = new HandlerThread("WindowInfosListenerForTpl"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } + } + } + + void addIgnoredWindowTokens(IBinder token) { + synchronized (mIgnoredWindowTokensLock) { + mIgnoredWindowTokens.add(token); + } + } + + void removeIgnoredWindowTokens(IBinder token) { + synchronized (mIgnoredWindowTokensLock) { + mIgnoredWindowTokens.remove(token); + } + } + + void registerListener(IBinder window, ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + startHandlerThreadIfNeeded(); + mHandler.post(() -> { + ProtoLog.d(WM_DEBUG_TPL, "Registering listener=%s with id=%d for window=%s with %s", + listener, id, window, thresholds); + + mRegisteredListeners.register(window, listener, thresholds, id); + registerWindowInfosListener(); + // Update the initial state for the new registered listener + computeTpl(mLastWindowHandles); + }); + } + + void unregisterListener(ITrustedPresentationListener listener, int id) { + startHandlerThreadIfNeeded(); + mHandler.post(() -> { + ProtoLog.d(WM_DEBUG_TPL, "Unregistering listener=%s with id=%d", + listener, id); + + mRegisteredListeners.unregister(listener, id); + if (mRegisteredListeners.isEmpty()) { + unregisterWindowInfosListener(); + } + }); + } + + void dump(PrintWriter pw) { + final String innerPrefix = " "; + pw.println("TrustedPresentationListenerController:"); + pw.println(innerPrefix + "Active unique listeners (" + + mRegisteredListeners.mUniqueListeners.size() + "):"); + for (int i = 0; i < mRegisteredListeners.mWindowToListeners.size(); i++) { + pw.println( + innerPrefix + " window=" + mRegisteredListeners.mWindowToListeners.keyAt(i)); + final var listeners = mRegisteredListeners.mWindowToListeners.valueAt(i); + for (int j = 0; j < listeners.size(); j++) { + final var listener = listeners.get(j); + pw.println(innerPrefix + innerPrefix + " listener=" + listener.mListener.asBinder() + + " id=" + listener.mId + + " thresholds=" + listener.mThresholds); + } + } + } + + private void registerWindowInfosListener() { + if (mWindowInfosListener != null) { + return; + } + + mWindowInfosListener = new WindowInfosListener() { + @Override + public void onWindowInfosChanged(InputWindowHandle[] windowHandles, + DisplayInfo[] displayInfos) { + mHandler.post(() -> computeTpl(windowHandles)); + } + }; + mLastWindowHandles = mWindowInfosListener.register().first; + } + + private void unregisterWindowInfosListener() { + if (mWindowInfosListener == null) { + return; + } + + mWindowInfosListener.unregister(); + mWindowInfosListener = null; + mLastWindowHandles = null; + } + + private void computeTpl(InputWindowHandle[] windowHandles) { + mLastWindowHandles = windowHandles; + if (mLastWindowHandles == null || mLastWindowHandles.length == 0 + || mRegisteredListeners.isEmpty()) { + return; + } + + Rect tmpRect = new Rect(); + Matrix tmpInverseMatrix = new Matrix(); + float[] tmpMatrix = new float[9]; + Region coveredRegionsAbove = new Region(); + long currTimeMs = System.currentTimeMillis(); + ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length); + + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates = + new ArrayMap<>(); + ArraySet<IBinder> ignoredWindowTokens; + synchronized (mIgnoredWindowTokensLock) { + ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens); + } + for (var windowHandle : mLastWindowHandles) { + if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) { + ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name); + continue; + } + tmpRect.set(windowHandle.frame); + var listeners = mRegisteredListeners.get(windowHandle.getWindowToken()); + if (listeners != null) { + Region region = new Region(); + region.op(tmpRect, coveredRegionsAbove, Region.Op.DIFFERENCE); + windowHandle.transform.invert(tmpInverseMatrix); + tmpInverseMatrix.getValues(tmpMatrix); + float scaleX = (float) Math.sqrt(tmpMatrix[MSCALE_X] * tmpMatrix[MSCALE_X] + + tmpMatrix[MSKEW_X] * tmpMatrix[MSKEW_X]); + float scaleY = (float) Math.sqrt(tmpMatrix[MSCALE_Y] * tmpMatrix[MSCALE_Y] + + tmpMatrix[MSKEW_Y] * tmpMatrix[MSKEW_Y]); + + float fractionRendered = computeFractionRendered(region, new RectF(tmpRect), + windowHandle.contentSize, + scaleX, scaleY); + + checkIfInThreshold(listeners, listenerUpdates, fractionRendered, windowHandle.alpha, + currTimeMs); + } + + coveredRegionsAbove.op(tmpRect, Region.Op.UNION); + ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s", + windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove); + } + + for (int i = 0; i < listenerUpdates.size(); i++) { + var updates = listenerUpdates.valueAt(i); + var listener = listenerUpdates.keyAt(i); + try { + listener.onTrustedPresentationChanged(updates.first.toArray(), + updates.second.toArray()); + } catch (RemoteException ignore) { + } + } + } + + private void addListenerUpdate( + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates, + ITrustedPresentationListener listener, int id, boolean presentationState) { + var updates = listenerUpdates.get(listener); + if (updates == null) { + updates = new Pair<>(new IntArray(), new IntArray()); + listenerUpdates.put(listener, updates); + } + if (presentationState) { + updates.first.add(id); + } else { + updates.second.add(id); + } + } + + + private void checkIfInThreshold( + ArrayList<TrustedPresentationInfo> listeners, + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates, + float fractionRendered, float alpha, long currTimeMs) { + ProtoLog.v(WM_DEBUG_TPL, "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d", + fractionRendered, alpha, currTimeMs); + for (int i = 0; i < listeners.size(); i++) { + var trustedPresentationInfo = listeners.get(i); + var listener = trustedPresentationInfo.mListener; + boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState; + boolean newState = + (alpha >= trustedPresentationInfo.mThresholds.minAlpha) && (fractionRendered + >= trustedPresentationInfo.mThresholds.minFractionRendered); + trustedPresentationInfo.mLastComputedTrustedPresentationState = newState; + + ProtoLog.v(WM_DEBUG_TPL, + "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f " + + "minFractionRendered=%f", + lastState, newState, alpha, trustedPresentationInfo.mThresholds.minAlpha, + fractionRendered, trustedPresentationInfo.mThresholds.minFractionRendered); + + if (lastState && !newState) { + // We were in the trusted presentation state, but now we left it, + // emit the callback if needed + if (trustedPresentationInfo.mLastReportedTrustedPresentationState) { + trustedPresentationInfo.mLastReportedTrustedPresentationState = false; + addListenerUpdate(listenerUpdates, listener, + trustedPresentationInfo.mId, /*presentationState*/ false); + ProtoLog.d(WM_DEBUG_TPL, "Adding untrusted state listener=%s with id=%d", + listener, trustedPresentationInfo.mId); + } + // Reset the timer + trustedPresentationInfo.mEnteredTrustedPresentationStateTime = -1; + } else if (!lastState && newState) { + // We were not in the trusted presentation state, but we entered it, begin the timer + // and make sure this gets called at least once more! + trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs; + mHandler.postDelayed(() -> { + computeTpl(mLastWindowHandles); + }, (long) (trustedPresentationInfo.mThresholds.stabilityRequirementMs * 1.5)); + } + + // Has the timer elapsed, but we are still in the state? Emit a callback if needed + if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && ( + currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime + > trustedPresentationInfo.mThresholds.stabilityRequirementMs)) { + trustedPresentationInfo.mLastReportedTrustedPresentationState = true; + addListenerUpdate(listenerUpdates, listener, + trustedPresentationInfo.mId, /*presentationState*/ true); + ProtoLog.d(WM_DEBUG_TPL, "Adding trusted state listener=%s with id=%d", + listener, trustedPresentationInfo.mId); + } + } + } + + private float computeFractionRendered(Region visibleRegion, RectF screenBounds, + Size contentSize, + float sx, float sy) { + ProtoLog.v(WM_DEBUG_TPL, + "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s " + + "scale=%f,%f", + visibleRegion, screenBounds, contentSize, sx, sy); + + if (contentSize.getWidth() == 0 || contentSize.getHeight() == 0) { + return -1; + } + if (screenBounds.width() == 0 || screenBounds.height() == 0) { + return -1; + } + + float fractionRendered = Math.min(sx * sy, 1.0f); + ProtoLog.v(WM_DEBUG_TPL, "fractionRendered scale=%f", fractionRendered); + + float boundsOverSourceW = screenBounds.width() / (float) contentSize.getWidth(); + float boundsOverSourceH = screenBounds.height() / (float) contentSize.getHeight(); + fractionRendered *= boundsOverSourceW * boundsOverSourceH; + ProtoLog.v(WM_DEBUG_TPL, "fractionRendered boundsOverSource=%f", fractionRendered); + // Compute the size of all the rects since they may be disconnected. + float[] visibleSize = new float[1]; + RegionUtils.forEachRect(visibleRegion, rect -> { + float size = rect.width() * rect.height(); + visibleSize[0] += size; + }); + + fractionRendered *= visibleSize[0] / (screenBounds.width() * screenBounds.height()); + return fractionRendered; + } + + private static class TrustedPresentationInfo { + boolean mLastComputedTrustedPresentationState = false; + boolean mLastReportedTrustedPresentationState = false; + long mEnteredTrustedPresentationStateTime = -1; + final TrustedPresentationThresholds mThresholds; + + final ITrustedPresentationListener mListener; + final int mId; + + private TrustedPresentationInfo(TrustedPresentationThresholds thresholds, int id, + ITrustedPresentationListener listener) { + mThresholds = thresholds; + mId = id; + mListener = listener; + checkValid(thresholds); + } + + private void checkValid(TrustedPresentationThresholds thresholds) { + if (thresholds.minAlpha <= 0 || thresholds.minFractionRendered <= 0 + || thresholds.stabilityRequirementMs < 1) { + throw new IllegalArgumentException( + "TrustedPresentationThresholds values are invalid"); + } + } + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 808a11d4adae..516d37c0136a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -866,6 +866,11 @@ public abstract class WindowManagerInternal { public abstract ImeTargetInfo onToggleImeRequested(boolean show, @NonNull IBinder focusedToken, @NonNull IBinder requestToken, int displayId); + /** + * Returns the token to identify the target window that the IME is associated with. + */ + public abstract @Nullable IBinder getTargetWindowTokenFromInputToken(IBinder inputToken); + /** The information of input method target when IME is requested to show or hide. */ public static class ImeTargetInfo { public final String focusedWindowName; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 72632dce2507..2125c63eed34 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -303,9 +303,11 @@ import android.window.AddToSurfaceSyncGroupResult; import android.window.ClientWindowFrames; import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; +import android.window.ITrustedPresentationListener; import android.window.ScreenCapture; import android.window.SystemPerformanceHinter; import android.window.TaskSnapshot; +import android.window.TrustedPresentationThresholds; import android.window.WindowContainerToken; import android.window.WindowContextInfo; @@ -764,6 +766,9 @@ public class WindowManagerService extends IWindowManager.Stub private final SurfaceSyncGroupController mSurfaceSyncGroupController = new SurfaceSyncGroupController(); + final TrustedPresentationListenerController mTrustedPresentationListenerController = + new TrustedPresentationListenerController(); + @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mDisplayInversionEnabledUri = @@ -7171,6 +7176,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(separator); } mSystemPerformanceHinter.dump(pw, ""); + mTrustedPresentationListenerController.dump(pw); } } @@ -7302,7 +7308,12 @@ public class WindowManagerService extends IWindowManager.Stub } } - MousePositionTracker mMousePositionTracker = new MousePositionTracker(); + // The mouse position tracker will be obsolete after the Pointer Icon Refactor. + // TODO(b/293587049): Remove after the refactoring is fully rolled out. + @Nullable + final MousePositionTracker mMousePositionTracker = + com.android.input.flags.Flags.enablePointerChoreographer() ? null + : new MousePositionTracker(); private static class MousePositionTracker implements PointerEventListener { private boolean mLatestEventWasMouse; @@ -7354,6 +7365,9 @@ public class WindowManagerService extends IWindowManager.Stub }; void updatePointerIcon(IWindow client) { + if (mMousePositionTracker == null) { + return; + } int pointerDisplayId; float mouseX, mouseY; @@ -7400,6 +7414,9 @@ public class WindowManagerService extends IWindowManager.Stub } void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) { + if (mMousePositionTracker == null) { + return; + } // Mouse position tracker has not been getting updates while dragging, update it now. if (!mMousePositionTracker.updatePosition( displayContent.getDisplayId(), latestX, latestY)) { @@ -7423,6 +7440,9 @@ public class WindowManagerService extends IWindowManager.Stub } } void setMousePointerDisplayId(int displayId) { + if (mMousePositionTracker == null) { + return; + } mMousePositionTracker.setPointerDisplayId(displayId); } @@ -8532,6 +8552,12 @@ public class WindowManagerService extends IWindowManager.Stub fromOrientations, toOrientations); } } + + @Override + public @Nullable IBinder getTargetWindowTokenFromInputToken(IBinder inputToken) { + InputTarget inputTarget = WindowManagerService.this.getInputTargetFromToken(inputToken); + return inputTarget == null ? null : inputTarget.getWindowToken(); + } } private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy { @@ -9771,4 +9797,17 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } } + + @Override + public void registerTrustedPresentationListener(IBinder window, + ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + mTrustedPresentationListenerController.registerListener(window, listener, thresholds, id); + } + + @Override + public void unregisterTrustedPresentationListener(ITrustedPresentationListener listener, + int id) { + mTrustedPresentationListenerController.unregisterListener(listener, id); + } } 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 2b18f0775047..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; @@ -915,7 +916,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio int i = mActivities.size(); while (i > 0) { i--; - mActivities.get(i).stopFreezingScreenLocked(true); + mActivities.get(i).stopFreezingScreen(true /* unfreezeNow */, true /* force */); } } } @@ -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/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b890a9e9bd04..4e9d23c88db4 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -178,6 +178,7 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY; import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import static com.android.window.flags.Flags.secureWindowState; import static com.android.window.flags.Flags.surfaceTrustedOverlay; @@ -668,6 +669,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean mSeamlesslyRotated = false; /** + * Whether the IME insets have been consumed. If {@code true}, this window won't be able to + * receive visible IME insets; {@code false}, otherwise. + */ + boolean mImeInsetsConsumed = false; + + /** * The insets state of sources provided by windows above the current window. */ final InsetsState mAboveInsetsState = new InsetsState(); @@ -1189,6 +1196,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow); parentWindow.addChild(this, sWindowSubLayerComparator); } + + if (token.mRoundedCornerOverlay) { + mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens( + getWindowToken()); + } } @Override @@ -1487,7 +1499,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (insetsChanged) { mWindowFrames.setInsetsChanged(false); - mWmService.mWindowsInsetsChanged--; + if (mWmService.mWindowsInsetsChanged > 0) { + mWmService.mWindowsInsetsChanged--; + } if (mWmService.mWindowsInsetsChanged == 0) { mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); } @@ -2393,6 +2407,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } mWmService.postWindowRemoveCleanupLocked(this); + + mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens( + getWindowToken()); } @Override @@ -3796,13 +3813,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ void notifyInsetsChanged() { ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this); - mWindowFrames.setInsetsChanged(true); + if (!mWindowFrames.hasInsetsChanged()) { + mWindowFrames.setInsetsChanged(true); - // If the new InsetsState won't be dispatched before releasing WM lock, the following - // message will be executed. - mWmService.mWindowsInsetsChanged++; - mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); - mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED); + // If the new InsetsState won't be dispatched before releasing WM lock, the following + // message will be executed. + mWmService.mWindowsInsetsChanged++; + mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); + mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED); + } final WindowContainer p = getParent(); if (p != null) { @@ -4192,6 +4211,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { pw.print("null"); } + pw.println(); if (mXOffset != 0 || mYOffset != 0) { pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset); @@ -4225,6 +4245,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (computeDragResizing()) { pw.println(prefix + "computeDragResizing=" + computeDragResizing()); } + if (mImeInsetsConsumed) { + pw.println(prefix + "mImeInsetsConsumed=true"); + } pw.println(prefix + "isOnScreen=" + isOnScreen()); pw.println(prefix + "isVisible=" + isVisible()); pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas @@ -5185,9 +5208,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this); if (voteChanged) { - getPendingTransaction().setFrameRate( - mSurfaceControl, mFrameRateVote.mRefreshRate, - mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS); + getPendingTransaction() + .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate, + mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS); + if (explicitRefreshRateHints()) { + getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl, + mFrameRateVote.mSelectionStrategy); + } } } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index aa589024fdc6..dff718a4b7d5 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -210,6 +210,10 @@ class WindowSurfacePlacer { return mInLayout; } + boolean isTraversalScheduled() { + return mTraversalScheduled; + } + void requestTraversal() { if (mTraversalScheduled) { return; diff --git a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java index 8c8f6a6cb386..193a0c8be60b 100644 --- a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java +++ b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java @@ -30,7 +30,7 @@ public class DisplayInfoOverrides { * Set of DisplayInfo fields that are overridden in DisplayManager using values from * WindowManager */ - public static final DisplayInfoFields WM_OVERRIDE_FIELDS = (out, source) -> { + public static final DisplayInfoFieldsUpdater WM_OVERRIDE_FIELDS = (out, source) -> { out.appWidth = source.appWidth; out.appHeight = source.appHeight; out.smallestNominalAppWidth = source.smallestNominalAppWidth; @@ -55,7 +55,7 @@ public class DisplayInfoOverrides { public static void copyDisplayInfoFields(@NonNull DisplayInfo out, @NonNull DisplayInfo base, @Nullable DisplayInfo override, - @NonNull DisplayInfoFields fields) { + @NonNull DisplayInfoFieldsUpdater fields) { out.copyFrom(base); if (override != null) { @@ -66,7 +66,7 @@ public class DisplayInfoOverrides { /** * Callback interface that allows to specify a subset of fields of DisplayInfo object */ - public interface DisplayInfoFields { + public interface DisplayInfoFieldsUpdater { /** * Copies a subset of fields from {@param source} to {@param out} * diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 24ee16389fd2..b19f3d813985 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -187,7 +187,7 @@ cc_defaults { "android.hardware.power.stats@1.0", "android.hardware.power.stats-V1-ndk", "android.hardware.thermal@1.0", - "android.hardware.thermal-V1-ndk", + "android.hardware.thermal-V2-ndk", "android.hardware.tv.input@1.0", "android.hardware.tv.input-V2-ndk", "android.hardware.vibrator-V2-cpp", diff --git a/services/core/jni/com_android_server_app_GameManagerService.cpp b/services/core/jni/com_android_server_app_GameManagerService.cpp index 3028813d0d5a..f593f4077365 100644 --- a/services/core/jni/com_android_server_app_GameManagerService.cpp +++ b/services/core/jni/com_android_server_app_GameManagerService.cpp @@ -25,15 +25,21 @@ namespace android { -static void android_server_app_GameManagerService_nativeSetOverrideFrameRate(JNIEnv* env, - jclass clazz, jint uid, - jfloat frameRate) { - SurfaceComposerClient::setOverrideFrameRate(uid, frameRate); +static void android_server_app_GameManagerService_nativeSetGameModeFrameRateOverride( + JNIEnv* env, jclass clazz, jint uid, jfloat frameRate) { + SurfaceComposerClient::setGameModeFrameRateOverride(uid, frameRate); +} + +static void android_server_app_GameManagerService_nativeSetGameDefaultFrameRateOverride( + JNIEnv* env, jclass clazz, jint uid, jfloat frameRate) { + SurfaceComposerClient::setGameDefaultFrameRateOverride(uid, frameRate); } static const JNINativeMethod gMethods[] = { - {"nativeSetOverrideFrameRate", "(IF)V", - (void*)android_server_app_GameManagerService_nativeSetOverrideFrameRate}, + {"nativeSetGameModeFrameRateOverride", "(IF)V", + (void*)android_server_app_GameManagerService_nativeSetGameModeFrameRateOverride}, + {"nativeSetGameDefaultFrameRateOverride", "(IF)V", + (void*)android_server_app_GameManagerService_nativeSetGameDefaultFrameRateOverride}, }; int register_android_server_app_GameManagerService(JNIEnv* env) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 6f65965b8aa8..bc05e77171bd 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -88,6 +88,7 @@ namespace input_flags = com::android::input::flags; namespace android { static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer(); +static const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl(); // The exponent used to calculate the pointer speed scaling factor. // The scaling factor is calculated as 2 ^ (speed * exponent), @@ -2737,6 +2738,15 @@ static void nativeSetStylusPointerIconEnabled(JNIEnv* env, jobject nativeImplObj im->setStylusPointerIconEnabled(enabled); } +static void nativeSetAccessibilityBounceKeysThreshold(JNIEnv* env, jobject nativeImplObj, + jint thresholdTimeMs) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + if (ENABLE_INPUT_FILTER_RUST) { + im->getInputManager()->getInputFilter().setAccessibilityBounceKeysThreshold( + static_cast<nsecs_t>(thresholdTimeMs) * 1000000); + } +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -2836,6 +2846,8 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeSetStylusButtonMotionEventsEnabled}, {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition}, {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled}, + {"setAccessibilityBounceKeysThreshold", "(I)V", + (void*)nativeSetAccessibilityBounceKeysThreshold}, }; #define FIND_CLASS(var, className) \ diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp index 3d4f866948af..2a0f1e2ede55 100644 --- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp +++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp @@ -182,7 +182,7 @@ static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) { auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE; auto hash_block_count = block_count; - for (auto i = 0; hash_block_count > 1; i++) { + while (hash_block_count > 1) { hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block; total_tree_block_count += hash_block_count; } @@ -468,7 +468,6 @@ private: borrowed_fd incomingFd, bool waitOnEof, std::vector<char>* buffer, std::vector<IncFsDataBlock>* blocks) { IncFsSize remaining = size; - IncFsSize totalSize = 0; IncFsBlockIndex blockIdx = 0; while (remaining > 0) { constexpr auto capacity = BUFFER_SIZE; @@ -502,7 +501,6 @@ private: buffer->resize(size + read); remaining -= read; - totalSize += read; } if (!buffer->empty() && !flashToIncFs(incfsFd, kind, true, &blockIdx, buffer, blocks)) { return false; 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/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 02032c786563..f69f6283f968 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -24,6 +24,7 @@ import android.content.pm.SigningDetails import android.os.Build import android.util.Slog import com.android.internal.os.RoSystemProperties +import com.android.internal.pm.permission.CompatibilityPermissionInfo import com.android.modules.utils.BinaryXmlPullParser import com.android.modules.utils.BinaryXmlSerializer import com.android.server.permission.access.AccessState @@ -42,7 +43,6 @@ import com.android.server.permission.access.util.hasBits import com.android.server.permission.access.util.isInternal import com.android.server.pm.KnownPackages import com.android.server.pm.parsing.PackageInfoUtils -import com.android.server.pm.permission.CompatibilityPermissionInfo import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 44609acf7894..a0fb0138e5e5 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -16,9 +16,7 @@ package com.android.server.permission.access.permission -import android.Manifest import android.permission.PermissionManager -import android.permission.flags.Flags import android.util.Slog import com.android.modules.utils.BinaryXmlPullParser import com.android.modules.utils.BinaryXmlSerializer @@ -61,7 +59,7 @@ class DevicePermissionPolicy : SchemePolicy() { } } - fun MutateStateScope.removeInactiveDevicesPermission(activePersistentDeviceIds: Set<String>) { + fun MutateStateScope.trimDevicePermissionStates(deviceIds: Set<String>) { newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdDevicePermissionFlags.forEachReversedIndexed { _, appId, _ -> val appIdDevicePermissionFlags = @@ -69,14 +67,11 @@ class DevicePermissionPolicy : SchemePolicy() { val devicePermissionFlags = appIdDevicePermissionFlags.mutate(appId) ?: return@forEachReversedIndexed - val removePersistentDeviceIds = mutableSetOf<String>() - devicePermissionFlags.forEachIndexed { _, deviceId, _ -> - if (!activePersistentDeviceIds.contains(deviceId)) { - removePersistentDeviceIds.add(deviceId) + devicePermissionFlags.forEachReversedIndexed { _, deviceId, _ -> + if (deviceId !in deviceIds) { + devicePermissionFlags -= deviceId } } - - removePersistentDeviceIds.forEach { deviceId -> devicePermissionFlags -= deviceId } } } } @@ -122,6 +117,10 @@ class DevicePermissionPolicy : SchemePolicy() { resetRuntimePermissions(packageName, userId) } + /** + * Reset permission states for all permissions requested by the given package, if no other + * package (sharing the App ID) request these permissions. + */ fun MutateStateScope.resetRuntimePermissions(packageName: String, userId: Int) { // It's okay to skip resetting permissions for packages that are removed, // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved() @@ -144,6 +143,7 @@ class DevicePermissionPolicy : SchemePolicy() { } } + // Trims permission state for permissions not requested by the App ID anymore. private fun MutateStateScope.trimPermissionStates(appId: Int) { val requestedPermissions = MutableIndexedSet<String>() forEachPackageInAppId(appId) { @@ -245,10 +245,6 @@ class DevicePermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { - if (!isDeviceAwarePermission(permissionName)) { - Slog.w(LOG_TAG, "$permissionName is not a device aware permission.") - return false - } val oldFlags = newState.userStates[userId]!! .appIdDevicePermissionFlags[appId] @@ -295,20 +291,8 @@ class DevicePermissionPolicy : SchemePolicy() { synchronized(listenersLock) { listeners = listeners + listener } } - private fun isDeviceAwarePermission(permissionName: String): Boolean = - DEVICE_AWARE_PERMISSIONS.contains(permissionName) - companion object { private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName - - /** These permissions are supported for virtual devices. */ - // TODO: b/298661870 - Use new API to get the list of device aware permissions. - val DEVICE_AWARE_PERMISSIONS = - if (Flags.deviceAwarePermissionApis()) { - setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) - } else { - emptySet<String>() - } } /** Listener for permission flags changes. */ diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index a7d32492d6e2..f469ab547763 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -1551,10 +1551,11 @@ class PermissionService(private val service: AccessCheckingService) : permissionName: String, deviceId: Int, ): Int { - return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) { + return if (!Flags.deviceAwarePermissionApisEnabled() || + deviceId == Context.DEVICE_ID_DEFAULT) { with(policy) { getPermissionFlags(appId, userId, permissionName) } } else { - if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { + if (permissionName !in DEVICE_AWARE_PERMISSIONS) { Slog.i( LOG_TAG, "$permissionName is not device aware permission, " + @@ -1586,10 +1587,11 @@ class PermissionService(private val service: AccessCheckingService) : deviceId: Int, flags: Int ): Boolean { - return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) { + return if (!Flags.deviceAwarePermissionApisEnabled() || + deviceId == Context.DEVICE_ID_DEFAULT) { with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } else { - if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { + if (permissionName !in DEVICE_AWARE_PERMISSIONS) { Slog.i( LOG_TAG, "$permissionName is not device aware permission, " + @@ -2312,20 +2314,19 @@ class PermissionService(private val service: AccessCheckingService) : override fun onSystemReady() { service.onSystemReady() + virtualDeviceManagerInternal = LocalServices.getService(VirtualDeviceManagerInternal::class.java) - virtualDeviceManagerInternal?.allPersistentDeviceIds?.let { persistentDeviceIds -> service.mutateState { - with(devicePolicy) { removeInactiveDevicesPermission(persistentDeviceIds) } + with(devicePolicy) { trimDevicePermissionStates(persistentDeviceIds) } } } - - // trim permission states for the external devices, when they are removed. virtualDeviceManagerInternal?.registerPersistentDeviceIdRemovedListener { persistentDeviceId -> service.mutateState { with(devicePolicy) { onDeviceIdRemoved(persistentDeviceId) } } } + permissionControllerManager = PermissionControllerManager(context, PermissionThread.getHandler()) } @@ -2860,5 +2861,14 @@ class PermissionService(private val service: AccessCheckingService) : PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + + /** These permissions are supported for virtual devices. */ + // TODO: b/298661870 - Use new API to get the list of device aware permissions. + val DEVICE_AWARE_PERMISSIONS = + if (Flags.deviceAwarePermissionApisEnabled()) { + setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) + } else { + emptySet<String>() + } } } diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java index d62da1a02525..5b222c01d56b 100644 --- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java +++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java @@ -57,11 +57,11 @@ import android.platform.test.annotations.Presubmit; import androidx.test.core.app.ApplicationProvider; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; import com.android.server.LocalServices; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.testing.shadows.ShadowApplicationPackageManager; import com.android.server.testing.shadows.ShadowUserManager; diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 3011fa17d4fc..5c4716dc751e 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -25,6 +25,7 @@ import android.os.Binder import android.os.UserHandle import android.util.ArrayMap import com.android.internal.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.parsing.pkg.PackageImpl import com.android.internal.pm.parsing.pkg.ParsedPackage import com.android.internal.pm.pkg.component.ParsedActivity import com.android.server.pm.AppsFilterImpl @@ -38,7 +39,6 @@ import com.android.server.pm.Settings import com.android.server.pm.SharedLibrariesImpl import com.android.server.pm.UserManagerInternal import com.android.server.pm.UserManagerService -import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.resolution.ComponentResolver import com.android.server.pm.snapshot.PackageDataSnapshot @@ -49,6 +49,8 @@ import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import com.android.server.wm.ActivityTaskManagerInternal import com.google.common.truth.Truth.assertThat +import java.io.File +import java.util.UUID import org.junit.After import org.junit.Before import org.junit.BeforeClass @@ -61,8 +63,6 @@ import org.mockito.Mockito.doReturn import org.mockito.Mockito.intThat import org.mockito.Mockito.same import org.testng.Assert.assertThrows -import java.io.File -import java.util.UUID @RunWith(Parameterized::class) class PackageManagerComponentLabelIconOverrideTest { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index 3461bb6b2c55..7277fd79fdd5 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -49,20 +49,20 @@ import android.util.SparseArray; import androidx.annotation.NonNull; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedActivityImpl; +import com.android.internal.pm.pkg.component.ParsedComponentImpl; +import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl; +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl; import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionImpl; +import com.android.internal.pm.pkg.component.ParsedProviderImpl; +import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.om.OverlayReferenceMapper; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedComponentImpl; -import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedPermissionImpl; -import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.utils.WatchableTester; import org.junit.Before; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java index f0d389be7cb6..b1c3e94c2138 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java @@ -32,11 +32,11 @@ import android.content.pm.ApplicationInfo; import android.os.Build; import android.platform.test.annotations.Presubmit; +import com.android.internal.pm.parsing.pkg.PackageImpl; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.parsing.PackageInfoUtils; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.PackageStateUnserialized; import com.android.server.pm.pkg.PackageUserStateImpl; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import org.junit.After; import org.junit.Before; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index a0dc2b68415c..b396cf498a67 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -63,10 +63,10 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.permission.persistence.RuntimePermissionsPersistence; import com.android.server.LocalServices; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.ArchiveState; @@ -817,12 +817,14 @@ public class PackageManagerSettingsTests { ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" }); ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 }); + ps1.setUsesSdkLibrariesOptional(new boolean[] {true}); ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM); settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1); assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true)); ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" }); ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 }); + ps2.setUsesSdkLibrariesOptional(new boolean[] {false}); settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2); settingsUnderTest.writeLPr(computer, /*sync=*/true); @@ -838,19 +840,30 @@ public class PackageManagerSettingsTests { Truth.assertThat(readPs1).isNotNull(); Truth.assertThat(readPs1.getUsesSdkLibraries()).isNotNull(); Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).isNotNull(); + Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).isNotNull(); Truth.assertThat(readPs2).isNotNull(); Truth.assertThat(readPs2.getUsesSdkLibraries()).isNotNull(); Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).isNotNull(); + Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).isNotNull(); List<Long> ps1VersionsAsList = new ArrayList<>(); for (long version : ps1.getUsesSdkLibrariesVersionsMajor()) { ps1VersionsAsList.add(version); } + List<Boolean> ps1RequireAsList = new ArrayList<>(); + for (boolean optional : ps1.getUsesSdkLibrariesOptional()) { + ps1RequireAsList.add(optional); + } + List<Long> ps2VersionsAsList = new ArrayList<>(); for (long version : ps2.getUsesSdkLibrariesVersionsMajor()) { ps2VersionsAsList.add(version); } + List<Boolean> ps2RequireAsList = new ArrayList<>(); + for (boolean optional : ps2.getUsesSdkLibrariesOptional()) { + ps2RequireAsList.add(optional); + } Truth.assertThat(readPs1.getUsesSdkLibraries()).asList() .containsExactlyElementsIn(ps1.getUsesSdkLibraries()).inOrder(); @@ -858,11 +871,17 @@ public class PackageManagerSettingsTests { Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).asList() .containsExactlyElementsIn(ps1VersionsAsList).inOrder(); + Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).asList() + .containsExactlyElementsIn(ps1RequireAsList).inOrder(); + Truth.assertThat(readPs2.getUsesSdkLibraries()).asList() .containsExactlyElementsIn(ps2.getUsesSdkLibraries()).inOrder(); Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).asList() .containsExactlyElementsIn(ps2VersionsAsList).inOrder(); + + Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).asList() + .containsExactlyElementsIn(ps2RequireAsList).inOrder(); } @Test @@ -1047,6 +1066,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1087,6 +1107,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1129,6 +1150,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1167,6 +1189,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1214,6 +1237,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1263,6 +1287,7 @@ public class PackageManagerSettingsTests { null /*usesSdkLibrariesVersions*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*mimeGroups*/, UUID.randomUUID(), 34 /*targetSdkVersion*/, @@ -1311,6 +1336,7 @@ public class PackageManagerSettingsTests { null /*usesSdkLibrariesVersions*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*mimeGroups*/, UUID.randomUUID(), 34 /*targetSdkVersion*/, @@ -1356,6 +1382,7 @@ public class PackageManagerSettingsTests { null /*usesSdkLibrariesVersions*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*mimeGroups*/, UUID.randomUUID(), 34 /*targetSdkVersion*/, diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java index 9c48af8ecd01..285c059e973a 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java @@ -73,7 +73,7 @@ import androidx.test.filters.Suppress; import com.android.compatibility.common.util.CddTest; import com.android.internal.content.InstallLocationUtils; import com.android.internal.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.parsing.ParsingUtils; import com.android.server.pm.test.service.server.R; import dalvik.system.VMRuntime; @@ -346,7 +346,7 @@ public class PackageManagerTests extends AndroidTestCase { private ParsedPackage parsePackage(Uri packageURI) { final String archiveFilePath = packageURI.getPath(); - ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime( + ParseResult<ParsedPackage> result = ParsingUtils.parseDefaultOneTime( new File(archiveFilePath), 0 /*flags*/, Collections.emptyList(), false /*collectCertificates*/); if (result.isError()) { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index ea88ec25b26e..03e45a27390f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -15,7 +15,7 @@ */ package com.android.server.pm; -import static com.android.server.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS; +import static com.android.internal.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -58,17 +58,28 @@ import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.permission.CompatibilityPermissionInfo; import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedActivityImpl; import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl; import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl; import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl; +import com.android.internal.pm.pkg.component.ParsedPermissionImpl; +import com.android.internal.pm.pkg.component.ParsedPermissionUtils; import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedProviderImpl; import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedServiceImpl; import com.android.internal.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.internal.util.ArrayUtils; import com.android.server.pm.parsing.PackageCacher; @@ -76,19 +87,8 @@ import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.PackageImpl; -import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; -import com.android.server.pm.pkg.component.ParsedPermissionImpl; -import com.android.server.pm.pkg.component.ParsedPermissionUtils; -import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedServiceImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import org.junit.Before; import org.junit.Rule; @@ -725,6 +725,16 @@ public class PackageParserTest { public boolean hasFeature(String feature) { return false; } + + @Override + public Set<String> getHiddenApiWhitelistedApps() { + return new ArraySet<>(); + } + + @Override + public Set<String> getInstallConstraintsAllowlist() { + return new ArraySet<>(); + } }); if (cacheDir != null) { setCacheDir(cacheDir); @@ -1062,7 +1072,7 @@ public class PackageParserTest { .addProtectedBroadcast("foo8") .setSdkLibraryName("sdk12") .setSdkLibVersionMajor(42) - .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"}) + .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"}, true) .setStaticSharedLibraryName("foo23") .setStaticSharedLibraryVersion(100) .addUsesStaticLibrary("foo23", 100, new String[]{"digest"}) diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java index decb44c2cd9b..c1271bb0ee36 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java @@ -50,13 +50,13 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.Pair; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.compat.PlatformCompat; import com.android.server.pm.parsing.PackageInfoUtils; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import org.hamcrest.BaseMatcher; @@ -376,7 +376,7 @@ public class ScanTests { // Create the ParsedPackage for the apex final ParsedPackage basicPackage = ((ParsedPackage) new PackageImpl(DUMMY_PACKAGE_NAME, codePath, codePath, - mock(TypedArray.class), false) + mock(TypedArray.class), false, null) .setVolumeUuid(UUID_ONE.toString()) .hideAsParsed()) .setVersionCodeMajor(1) @@ -595,12 +595,12 @@ public class ScanTests { // TODO(b/135203078): Make this use PackageImpl.forParsing and separate the steps return (ParsingPackage) ((ParsedPackage) new PackageImpl(packageName, "/data/tmp/randompath/base.apk", createCodePath(packageName), - mock(TypedArray.class), false) + mock(TypedArray.class), false, null) .setVolumeUuid(UUID_ONE.toString()) .addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"}) .addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"}) - .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"}) - .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"}) + .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"}, false) + .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"}, true) .hideAsParsed()) .setNativeLibraryRootDir("/data/tmp/randompath/base.apk:/lib") .setVersionCodeMajor(1) @@ -628,6 +628,7 @@ public class ScanTests { assertThat(pkgSetting.getUsesSdkLibraries(), arrayContaining("some.sdk.library", "some.other.sdk.library")); assertThat(pkgSetting.getUsesSdkLibrariesVersionsMajor(), is(new long[]{123L, 789L})); + assertThat(pkgSetting.getUsesSdkLibrariesOptional(), is(new boolean[]{false, true})); assertThat(pkgSetting.getPkg(), is(scanResult.mRequest.mParsedPackage)); assertThat(pkgSetting.getPath(), is(new File(createCodePath(packageName)))); assertThat(pkgSetting.getVersionCode(), diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index b102ab4f7e3b..b63950c10a18 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -39,14 +39,14 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.component.ParsedActivityUtils; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionUtils; import com.android.internal.util.ArrayUtils; import com.android.server.pm.PackageManagerException; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedActivityUtils; -import com.android.server.pm.pkg.component.ParsedPermissionUtils; import com.android.server.pm.test.service.server.R; import com.google.common.truth.Expect; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt index 67b91d2646d9..c435b94fcacd 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt @@ -22,7 +22,6 @@ import android.content.pm.parsing.result.ParseResult import android.platform.test.annotations.Presubmit import androidx.test.InstrumentationRegistry import com.android.internal.pm.parsing.pkg.ParsedPackage -import com.android.server.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.test.service.server.R import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage @@ -121,7 +120,7 @@ class PackageParsingDeferErrorTest { input.copyTo(output) } } - return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(), + return ParsingUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(), false /*collectCertificates*/) } } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java new file mode 100644 index 000000000000..a9eac95dfd87 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.parsing; + +import android.annotation.NonNull; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; +import android.content.res.TypedArray; +import android.permission.PermissionManager; + +import com.android.internal.pm.parsing.pkg.PackageImpl; +import com.android.internal.pm.parsing.pkg.ParsedPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.SystemConfig; + +import java.io.File; +import java.util.List; +import java.util.Set; + +/** @hide **/ +public class ParsingUtils { + + /** + * @see ParsingPackageUtils#parseDefault(ParseInput, File, int, List, boolean, + * ParsingPackageUtils.Callback) + */ + @NonNull + public static ParseResult<ParsedPackage> parseDefaultOneTime(File file, + @ParsingPackageUtils.ParseFlags int parseFlags, + @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions, + boolean collectCertificates) { + ParseInput input = ParseTypeImpl.forDefaultParsing().reset(); + return ParsingPackageUtils.parseDefault(input, file, parseFlags, splitPermissions, collectCertificates, + new ParsingPackageUtils.Callback() { + @Override + public boolean hasFeature(String feature) { + // Assume the device doesn't support anything. This will affect permission + // parsing and will force <uses-permission/> declarations to include all + // requiredNotFeature permissions and exclude all requiredFeature + // permissions. This mirrors the old behavior. + return false; + } + + @Override + public ParsingPackage startParsingPackage( + @NonNull String packageName, + @NonNull String baseApkPath, + @NonNull String path, + @NonNull TypedArray manifestArray, boolean isCoreApp) { + return PackageImpl.forParsing(packageName, baseApkPath, path, manifestArray, + isCoreApp, this); + } + + @Override + public Set<String> getHiddenApiWhitelistedApps() { + return SystemConfig.getInstance().getHiddenApiWhitelistedApps(); + } + + @Override + public Set<String> getInstallConstraintsAllowlist() { + return SystemConfig.getInstance().getInstallConstraintsAllowlist(); + } + }); + } +} diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt index 1f57b6c9f95f..98af63c65e90 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt @@ -17,15 +17,15 @@ package com.android.server.pm.parsing import android.content.pm.PackageManager -import com.android.server.pm.pkg.parsing.ParsingPackageUtils import android.platform.test.annotations.Postsubmit +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.PackageManagerException import com.android.server.pm.PackageManagerService import com.android.server.pm.PackageManagerServiceUtils +import java.io.File import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import java.io.File /** * This test parses all the system APKs on the device image to ensure that they succeed. diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java index 6cd71237efa2..9517e49c4a73 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java @@ -24,8 +24,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java index 27fd781584f2..01fad8f57d16 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java index b13d6de55cf6..b1f26c253043 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java @@ -23,8 +23,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java index fa69f844c33a..349763ae18b3 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java @@ -24,9 +24,9 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.library.PackageBackwardCompatibility.AndroidTestRunnerSplitUpdater; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java index 856013a96017..71bdacbce9f7 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java @@ -22,9 +22,9 @@ import android.util.ArrayMap; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.SystemConfig; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Before; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java index ae5ea21aec4e..6aa0c2db4d39 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java @@ -21,8 +21,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java index e126ffcab5ff..44098d0be2b2 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java @@ -23,8 +23,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java index d0b0cf894340..9d5ce8a66395 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java @@ -28,10 +28,10 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Assume; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java index c141c0337540..bffeb7255b31 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java @@ -23,9 +23,9 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java index a58604b81e06..b114cd375050 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java @@ -23,9 +23,9 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryOrgApacheHttpLegacyLibrary; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.Test; diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index 170faf61858b..ef9c62fd3795 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -30,21 +30,21 @@ import android.util.ArraySet import android.util.SparseArray import android.util.SparseIntArray import com.android.internal.R -import com.android.server.pm.parsing.pkg.AndroidPackageUtils -import com.android.server.pm.parsing.pkg.PackageImpl +import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils +import com.android.internal.pm.parsing.pkg.PackageImpl +import com.android.internal.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedApexSystemServiceImpl +import com.android.internal.pm.pkg.component.ParsedAttributionImpl +import com.android.internal.pm.pkg.component.ParsedComponentImpl +import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl +import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl +import com.android.internal.pm.pkg.component.ParsedPermissionImpl +import com.android.internal.pm.pkg.component.ParsedProcessImpl +import com.android.internal.pm.pkg.component.ParsedProviderImpl +import com.android.internal.pm.pkg.component.ParsedServiceImpl +import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl -import com.android.server.pm.pkg.component.ParsedAttributionImpl -import com.android.server.pm.pkg.component.ParsedComponentImpl -import com.android.server.pm.pkg.component.ParsedInstrumentationImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl -import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl -import com.android.server.pm.pkg.component.ParsedPermissionImpl -import com.android.server.pm.pkg.component.ParsedProcessImpl -import com.android.server.pm.pkg.component.ParsedProviderImpl -import com.android.server.pm.pkg.component.ParsedServiceImpl -import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import java.security.KeyPairGenerator @@ -127,6 +127,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag "getUsesSdkLibraries", "getUsesSdkLibrariesVersionsMajor", "getUsesSdkLibrariesCertDigests", + "getUsesSdkLibrariesOptional", // Tested through addUsesStaticLibrary "addUsesStaticLibrary", "getUsesStaticLibraries", @@ -533,34 +534,34 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag } ), getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")), - getSetByValue({ AndroidPackageUtils.isOdm(it) }, "isOdm", PackageImpl::setOdm, true), - getSetByValue({ AndroidPackageUtils.isOem(it) }, "isOem", PackageImpl::setOem, true), + getSetByValue({ AndroidPackageLegacyUtils.isOdm(it) }, "isOdm", PackageImpl::setOdm, true), + getSetByValue({ AndroidPackageLegacyUtils.isOem(it) }, "isOem", PackageImpl::setOem, true), getSetByValue( - { AndroidPackageUtils.isPrivileged(it) }, + { AndroidPackageLegacyUtils.isPrivileged(it) }, "isPrivileged", PackageImpl::setPrivileged, true ), getSetByValue( - { AndroidPackageUtils.isProduct(it) }, + { AndroidPackageLegacyUtils.isProduct(it) }, "isProduct", PackageImpl::setProduct, true ), getSetByValue( - { AndroidPackageUtils.isVendor(it) }, + { AndroidPackageLegacyUtils.isVendor(it) }, "isVendor", PackageImpl::setVendor, true ), getSetByValue( - { AndroidPackageUtils.isSystem(it) }, + { AndroidPackageLegacyUtils.isSystem(it) }, "isSystem", PackageImpl::setSystem, true ), getSetByValue( - { AndroidPackageUtils.isSystemExt(it) }, + { AndroidPackageLegacyUtils.isSystemExt(it) }, "isSystemExt", PackageImpl::setSystemExt, true @@ -592,7 +593,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag ) ) { "" } }, - true + true, null ) .asSplit( arrayOf("testSplitNameZero", "testSplitNameOne"), @@ -608,7 +609,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag .setSplitHasCode(1, false) .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero") .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne") - .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1")) + .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"), true) .addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2")) override fun finalizeObject(parcelable: Parcelable) { @@ -661,6 +662,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag expect.that(after.usesSdkLibrariesCertDigests!!.size).isEqualTo(1) expect.that(after.usesSdkLibrariesCertDigests!![0]).asList() .containsExactly("testCertDigest1") + expect.that(after.usesSdkLibrariesOptional).asList().containsExactly(true) expect.that(after.usesStaticLibraries).containsExactly("testStatic") expect.that(after.usesStaticLibrariesVersions).asList().containsExactly(3L) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt index 26468544e8d6..2c8b1cd884f1 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt @@ -18,7 +18,7 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.ActivityInfo import com.android.internal.pm.pkg.component.ParsedActivity -import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedActivityImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt index 52d5b3bccb72..ad5374672d22 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import com.android.internal.pm.pkg.component.ParsedAttribution -import com.android.server.pm.pkg.component.ParsedAttributionImpl +import com.android.internal.pm.pkg.component.ParsedAttributionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt index af0c0de2db15..3ac48536b39c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PackageManager import com.android.internal.pm.pkg.component.ParsedComponent -import com.android.server.pm.pkg.component.ParsedComponentImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl +import com.android.internal.pm.pkg.component.ParsedComponentImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import android.os.Bundle import android.os.Parcelable import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt index dc0f194f10cc..2bd4f617dcb5 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import com.android.internal.pm.pkg.component.ParsedInstrumentation -import com.android.server.pm.pkg.component.ParsedInstrumentationImpl +import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt index 5224f23d38d1..af385e202a9e 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import com.android.internal.pm.pkg.component.ParsedIntentInfo -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import android.os.Parcelable import android.os.PatternMatcher import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt index dfff6025e2eb..061e39ddf6df 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import com.android.internal.pm.pkg.component.ParsedMainComponent -import com.android.server.pm.pkg.component.ParsedMainComponentImpl +import com.android.internal.pm.pkg.component.ParsedMainComponentImpl import android.os.Parcelable import java.util.Arrays import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt index ccbf558734d3..3a6418824dfa 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import com.android.internal.pm.pkg.component.ParsedPermissionGroup -import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl +import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt index 2814783b6849..551f16d2a3bb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.test.parsing.parcelling import com.android.internal.pm.pkg.component.ParsedPermission import com.android.internal.pm.pkg.component.ParsedPermissionGroup -import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl -import com.android.server.pm.pkg.component.ParsedPermissionImpl +import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl +import com.android.internal.pm.pkg.component.ParsedPermissionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt index 2e9604696acb..93bdeaeb90e3 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt @@ -16,9 +16,9 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.internal.pm.pkg.component.ParsedProcess -import com.android.server.pm.pkg.component.ParsedProcessImpl import android.util.ArrayMap +import com.android.internal.pm.pkg.component.ParsedProcess +import com.android.internal.pm.pkg.component.ParsedProcessImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt index 290dbd6277b6..1e844705fe3c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt @@ -17,9 +17,9 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PathPermission -import com.android.internal.pm.pkg.component.ParsedProvider -import com.android.server.pm.pkg.component.ParsedProviderImpl import android.os.PatternMatcher +import com.android.internal.pm.pkg.component.ParsedProvider +import com.android.internal.pm.pkg.component.ParsedProviderImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt index 3ae7e9220cc6..79d5a4f7030a 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import com.android.internal.pm.pkg.component.ParsedService -import com.android.server.pm.pkg.component.ParsedServiceImpl +import com.android.internal.pm.pkg.component.ParsedServiceImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt index 67dfc6d4f7ef..d0ad09be982b 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import com.android.internal.pm.pkg.component.ParsedUsesPermission -import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl +import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt index 1da3a2234ffc..b21c34905bad 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt @@ -17,37 +17,34 @@ package com.android.server.pm.test.pkg import android.content.Intent -import android.content.pm.overlay.OverlayPaths import android.content.pm.PackageManager import android.content.pm.PathPermission import android.content.pm.SharedLibraryInfo import android.content.pm.VersionedPackage +import android.content.pm.overlay.OverlayPaths import android.os.PatternMatcher import android.util.ArraySet +import com.android.internal.pm.parsing.pkg.PackageImpl import com.android.internal.pm.pkg.component.ParsedActivity +import com.android.internal.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedComponentImpl import com.android.internal.pm.pkg.component.ParsedInstrumentation +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import com.android.internal.pm.pkg.component.ParsedPermission import com.android.internal.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedPermissionImpl import com.android.internal.pm.pkg.component.ParsedProcess +import com.android.internal.pm.pkg.component.ParsedProcessImpl import com.android.internal.pm.pkg.component.ParsedProvider +import com.android.internal.pm.pkg.component.ParsedProviderImpl import com.android.internal.pm.pkg.component.ParsedService import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSettingBuilder -import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.pm.pkg.PackageUserState -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedComponentImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl -import com.android.server.pm.pkg.component.ParsedPermissionImpl -import com.android.server.pm.pkg.component.ParsedProcessImpl -import com.android.server.pm.pkg.component.ParsedProviderImpl import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest import com.google.common.truth.Expect -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder import kotlin.contracts.ExperimentalContracts import kotlin.reflect.KClass import kotlin.reflect.KFunction @@ -55,6 +52,9 @@ import kotlin.reflect.KType import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.full.memberFunctions import kotlin.reflect.full.starProjectedType +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder class PackageStateTest { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt index 9341e9d96335..5e73d198f528 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt @@ -22,11 +22,11 @@ import android.os.Build import android.os.PatternMatcher import android.util.ArraySet import com.android.internal.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.SystemConfig import com.android.server.compat.PlatformCompat import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationCollector import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index a737b9097b53..d307608e0be2 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -29,17 +29,22 @@ import android.util.IndentingPrintWriter import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.Computer import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationEnforcer import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever +import java.util.UUID +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertFailsWith +import kotlin.test.fail import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -51,11 +56,6 @@ import org.mockito.Mockito.doReturn import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.verifyNoMoreInteractions -import java.util.UUID -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicInteger -import kotlin.test.assertFailsWith -import kotlin.test.fail @RunWith(Parameterized::class) class DomainVerificationEnforcerTest { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt index f38df22af630..5edf30a33def 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -30,24 +30,24 @@ import android.os.Process import android.util.ArraySet import android.util.SparseArray import com.android.internal.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationManagerStub import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat +import java.util.UUID +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.assertFailsWith import org.junit.Test import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString import org.mockito.Mockito.doReturn -import java.util.UUID -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.test.assertFailsWith class DomainVerificationManagerApiTest { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index 874e0d2bbc9a..85f012534113 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -37,27 +37,27 @@ import android.util.ArraySet import android.util.SparseArray import android.util.Xml import com.android.internal.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.Computer import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.testutils.mock import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.spy import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.security.PublicKey +import java.util.UUID import org.junit.Test import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString import org.mockito.Mockito.doReturn -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.security.PublicKey -import java.util.UUID class DomainVerificationPackageTest { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 3207e6c2a411..a5c4f6cc0289 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -25,15 +25,16 @@ import android.os.Process import android.util.ArraySet import android.util.SparseArray import com.android.internal.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever +import java.util.UUID import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -44,7 +45,6 @@ import org.mockito.Mockito.anyString import org.mockito.Mockito.doReturn import org.mockito.Mockito.eq import org.mockito.Mockito.verify -import java.util.UUID @RunWith(Parameterized::class) class DomainVerificationSettingsMutationTest { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index a90b7d5ec7da..ae570a3a0ee2 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -27,14 +27,15 @@ import android.os.Process import android.util.ArraySet import android.util.SparseArray import com.android.internal.pm.parsing.pkg.AndroidPackageInternal +import com.android.internal.pm.pkg.component.ParsedActivityImpl +import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat +import java.util.UUID import org.junit.Test import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt @@ -42,8 +43,6 @@ import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString import org.mockito.Mockito.doReturn -import java.util.UUID - class DomainVerificationUserStateOverrideTest { companion object { diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt index 832136cec792..925dad8b6661 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionDefinitionsTest.kt @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized * AppIdPermissionPolicyPermissionStatesTest because these concepts don't apply to onUserAdded(). */ @RunWith(Parameterized::class) -class AppIdPermissionPolicyPermissionDefinitionsTest : BaseAppIdPermissionPolicyTest() { +class AppIdPermissionPolicyPermissionDefinitionsTest : BasePermissionPolicyTest() { @Parameterized.Parameter(0) lateinit var action: Action @Test diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt index 05477197b991..12370954e9a5 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt @@ -29,7 +29,7 @@ import org.junit.runners.Parameterized * and resetRuntimePermissions() in AppIdPermissionPolicy */ @RunWith(Parameterized::class) -class AppIdPermissionPolicyPermissionResetTest : BaseAppIdPermissionPolicyTest() { +class AppIdPermissionPolicyPermissionResetTest : BasePermissionPolicyTest() { @Parameterized.Parameter(0) lateinit var action: Action @Test diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt index c44b2c50258e..6b9c9c2b4abc 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized * states for onUserAdded(), onStorageVolumeAdded() and onPackageAdded() in AppIdPermissionPolicy */ @RunWith(Parameterized::class) -class AppIdPermissionPolicyPermissionStatesTest : BaseAppIdPermissionPolicyTest() { +class AppIdPermissionPolicyPermissionStatesTest : BasePermissionPolicyTest() { @Parameterized.Parameter(0) lateinit var action: Action @Before diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt index e4e336845fca..cde46abafe95 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt @@ -30,7 +30,7 @@ import org.junit.Test import org.mockito.Mockito.times import org.mockito.Mockito.verify -class AppIdPermissionPolicyTest : BaseAppIdPermissionPolicyTest() { +class AppIdPermissionPolicyTest : BasePermissionPolicyTest() { @Test fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() { val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0) diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BasePermissionPolicyTest.kt index 316f3387c539..7b3f21603c0a 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BasePermissionPolicyTest.kt @@ -53,7 +53,7 @@ import org.mockito.ArgumentMatchers.anyLong * Mocking unit test for AppIdPermissionPolicy. */ @RunWith(AndroidJUnit4::class) -abstract class BaseAppIdPermissionPolicyTest { +abstract class BasePermissionPolicyTest { protected lateinit var oldState: MutableAccessState protected lateinit var newState: MutableAccessState diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/DevicePermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/DevicePermissionPolicyTest.kt new file mode 100644 index 000000000000..996dd9dcba4b --- /dev/null +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/DevicePermissionPolicyTest.kt @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.test + +import com.android.server.permission.access.GetStateScope +import com.android.server.permission.access.MutableAccessState +import com.android.server.permission.access.MutableDevicePermissionFlags +import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.permission.DevicePermissionPolicy +import com.android.server.permission.access.permission.PermissionFlags +import com.android.server.testutils.mock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +/** + * This class tests permissions for external devices, we have separate policy to + * manage external device permissions. + */ +class DevicePermissionPolicyTest : BasePermissionPolicyTest() { + private val devicePermissionPolicy = DevicePermissionPolicy() + + @Test + fun testOnAppIdRemoved_clearPermissionFlags() { + val packageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)) + ) + addPackageState(packageState) + setPermissionFlags( + APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED + ) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + + mutateState { + with(devicePermissionPolicy) { + onAppIdRemoved(APP_ID_1) + } + } + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0)) + .isEqualTo(0) + } + + @Test + fun testOnDeviceIdRemoved_clearPermissionFlags() { + val requestingPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)) + ) + addPackageState(requestingPackageState) + setPermissionFlags( + APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED + ) + setPermissionFlags( + APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED + ) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + + mutateState { + with(devicePermissionPolicy) { + onDeviceIdRemoved(DEVICE_ID_1) + } + } + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0)) + .isEqualTo(0) + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0)) + .isEqualTo(PermissionFlags.RUNTIME_GRANTED) + } + + @Test + fun testRemoveInactiveDevicesPermission_clearPermissionFlags() { + val requestingPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)) + ) + addPackageState(requestingPackageState) + setPermissionFlags( + APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED + ) + setPermissionFlags( + APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED + ) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + + mutateState { + with(devicePermissionPolicy) { + trimDevicePermissionStates(setOf(DEVICE_ID_2)) + } + } + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0)) + .isEqualTo(0) + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_2, USER_ID_0, PERMISSION_NAME_0)) + .isEqualTo(PermissionFlags.RUNTIME_GRANTED) + } + + @Test + fun testOnStateMutated_notEmpty_isCalledForEachListener() { + val mockListener = + mock<DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener> {} + devicePermissionPolicy.addOnPermissionFlagsChangedListener(mockListener) + + GetStateScope(oldState).apply { + with(devicePermissionPolicy) { + onStateMutated() + } + } + + verify(mockListener, times(1)).onStateMutated() + } + + @Test + fun testOnStorageVolumeMounted_trimsPermissionsNotRequestAnymore() { + val packageState = mockPackageState( + APP_ID_1, + mockAndroidPackage( + PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_1, PERMISSION_NAME_0) + ) + ) + addPackageState(packageState) + setPermissionFlags( + APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, PermissionFlags.RUNTIME_GRANTED + ) + setPermissionFlags( + APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED + ) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + + val installedPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_1)) + ) + addPackageState(installedPackageState) + + mutateState { + with(devicePermissionPolicy) { + onStorageVolumeMounted(null, listOf(PACKAGE_NAME_1), false) + } + } + + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1)) + .isEqualTo(PermissionFlags.RUNTIME_GRANTED) + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0)) + .isEqualTo(0) + } + + + @Test + fun testResetRuntimePermissions_trimsPermissionStates() { + val packageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_1)) + ) + addPackageState(packageState) + setPermissionFlags( + APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, PermissionFlags.RUNTIME_GRANTED + ) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + + mutateState { + with(devicePermissionPolicy) { + resetRuntimePermissions(PACKAGE_NAME_1, USER_ID_0) + } + } + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1)) + .isEqualTo(0) + } + + @Test + fun testResetRuntimePermissions_keepsPermissionStates() { + val packageState = mockPackageState( + APP_ID_1, + mockAndroidPackage( + PACKAGE_NAME_1, + requestedPermissions = setOf(PERMISSION_NAME_1, PERMISSION_NAME_0) + ) + ) + val packageState2 = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_2, requestedPermissions = setOf(PERMISSION_NAME_0)) + ) + addPackageState(packageState) + addPackageState(packageState2) + setPermissionFlags( + APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, PermissionFlags.RUNTIME_GRANTED + ) + setPermissionFlags( + APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED + ) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + assertThat( + getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0, oldState) + ).isEqualTo(PermissionFlags.RUNTIME_GRANTED) + + mutateState { + with(devicePermissionPolicy) { + resetRuntimePermissions(PACKAGE_NAME_1, USER_ID_0) + } + } + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_1)) + .isEqualTo(0) + assertThat(getPermissionFlags(APP_ID_1, DEVICE_ID_1, USER_ID_0, PERMISSION_NAME_0)) + .isEqualTo(PermissionFlags.RUNTIME_GRANTED) + } + + private fun getPermissionFlags( + appId: Int, + deviceId: String, + userId: Int, + permissionName: String, + state: MutableAccessState = newState + ): Int = state.userStates[userId] + ?.appIdDevicePermissionFlags + ?.get(appId) + ?.get(deviceId) + ?.getWithDefault(permissionName, 0) + ?: 0 + + + private fun setPermissionFlags( + appId: Int, + deviceId: String, + userId: Int, + permissionName: String, + newFlags: Int, + state: MutableAccessState = oldState + ) { + val appIdDevicePermissionFlags = + state.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags() + val devicePermissionFlags = + appIdDevicePermissionFlags.mutateOrPut(appId) { MutableDevicePermissionFlags() } + val permissionFlags = + devicePermissionFlags.mutateOrPut(deviceId) { MutableIndexedMap() } + permissionFlags.putWithDefault(permissionName, newFlags, 0) + if (permissionFlags.isEmpty()) { + devicePermissionFlags -= deviceId + if (devicePermissionFlags.isEmpty()) { + appIdDevicePermissionFlags -= appId + } + } + } + + companion object { + private const val DEVICE_ID_1 = "cdm:1" + private const val DEVICE_ID_2 = "cdm:2" + } +} 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/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 6906decec9a2..9739e4b46063 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -18,7 +18,10 @@ package com.android.server.app; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE; +import static com.android.server.app.GameManagerService.Injector; import static com.android.server.app.GameManagerService.LOADING_BOOST_MAX_DURATION; +import static com.android.server.app.GameManagerService.PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED; +import static com.android.server.app.GameManagerService.PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE; import static com.android.server.app.GameManagerService.SET_GAME_STATE; import static com.android.server.app.GameManagerService.WRITE_DELAY_MILLIS; import static com.android.server.app.GameManagerService.WRITE_GAME_MODE_INTERVENTION_LIST_FILE; @@ -33,6 +36,7 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.any; @@ -72,9 +76,12 @@ import android.os.IBinder; import android.os.PowerManagerInternal; import android.os.RemoteException; import android.os.UserManager; +import android.os.test.FakePermissionEnforcer; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; +import android.server.app.Flags; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -85,6 +92,7 @@ import com.android.server.SystemService; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -108,6 +116,7 @@ import java.util.function.Supplier; @Presubmit public class GameManagerServiceTests { @Mock MockContext mMockContext; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String TAG = "GameManagerServiceTests"; private static final String PACKAGE_NAME_INVALID = "com.android.app"; private static final int USER_ID_1 = 1001; @@ -126,6 +135,11 @@ public class GameManagerServiceTests { private UserManager mMockUserManager; private BroadcastReceiver mShutDownActionReceiver; + private FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer(); + + @Mock + private GameManagerServiceSystemPropertiesWrapper mSysPropsMock; + @Captor ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor; @@ -193,6 +207,8 @@ public class GameManagerServiceTests { switch (name) { case Context.USER_SERVICE: return mMockUserManager; + case Context.PERMISSION_ENFORCER_SERVICE: + return mFakePermissionEnforcer; } throw new UnsupportedOperationException("Couldn't find system service: " + name); } @@ -222,6 +238,8 @@ public class GameManagerServiceTests { when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn( DEFAULT_PACKAGE_UID); LocalServices.addService(PowerManagerInternal.class, mMockPowerManager); + + mSetFlagsRule.enableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE); } private void mockAppCategory(String packageName, @ApplicationInfo.Category int category) @@ -1695,9 +1713,8 @@ public class GameManagerServiceTests { mockModifyGameModeGranted(); final Context context = InstrumentationRegistry.getContext(); GameManagerService gameManagerService = - new GameManagerService(mMockContext, - mTestLooper.getLooper(), - context.getFilesDir()); + new GameManagerService(mMockContext, mTestLooper.getLooper(), context.getFilesDir(), + new Injector()); startUser(gameManagerService, USER_ID_1); startUser(gameManagerService, USER_ID_2); @@ -1786,7 +1803,7 @@ public class GameManagerServiceTests { mockDeviceConfigBattery(); final Context context = InstrumentationRegistry.getContext(); GameManagerService gameManagerService = new GameManagerService(mMockContext, - mTestLooper.getLooper(), context.getFilesDir()); + mTestLooper.getLooper(), context.getFilesDir(), new Injector()); startUser(gameManagerService, USER_ID_1); startUser(gameManagerService, USER_ID_2); gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1); @@ -1962,7 +1979,7 @@ public class GameManagerServiceTests { assertTrue( gameManagerService.mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, USER_ID_1)); - Mockito.verify(gameManagerService).setOverrideFrameRate( + Mockito.verify(gameManagerService).setGameModeFrameRateOverride( ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(60.0f)); checkFps(gameManagerService, GameManager.GAME_MODE_CUSTOM, 60); @@ -2035,7 +2052,7 @@ public class GameManagerServiceTests { mTestLooper.getLooper())); startUser(gameManagerService, USER_ID_1); gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); - Mockito.verify(gameManagerService).setOverrideFrameRate( + Mockito.verify(gameManagerService).setGameModeFrameRateOverride( ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(90.0f)); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); @@ -2044,7 +2061,7 @@ public class GameManagerServiceTests { when(DeviceConfig.getProperty(anyString(), anyString())) .thenReturn(configStringAfter); gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName); - Mockito.verify(gameManagerService).setOverrideFrameRate( + Mockito.verify(gameManagerService).setGameModeFrameRateOverride( ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(0.0f)); } @@ -2061,14 +2078,14 @@ public class GameManagerServiceTests { mTestLooper.getLooper())); startUser(gameManagerService, USER_ID_1); gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); - Mockito.verify(gameManagerService).setOverrideFrameRate( + Mockito.verify(gameManagerService).setGameModeFrameRateOverride( ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(90.0f)); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); mockInterventionsDisabledNoOptInFromXml(); gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName); - Mockito.verify(gameManagerService).setOverrideFrameRate( + Mockito.verify(gameManagerService).setGameModeFrameRateOverride( ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(0.0f)); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0); @@ -2087,14 +2104,14 @@ public class GameManagerServiceTests { startUser(gameManagerService, USER_ID_1); gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); - Mockito.verify(gameManagerService).setOverrideFrameRate( + Mockito.verify(gameManagerService).setGameModeFrameRateOverride( ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(90.0f)); checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 90); mockInterventionsEnabledAllOptInFromXml(); gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName); - Mockito.verify(gameManagerService).setOverrideFrameRate( + Mockito.verify(gameManagerService).setGameModeFrameRateOverride( ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(0.0f)); } @@ -2390,4 +2407,140 @@ public class GameManagerServiceTests { DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false); } + + @Test + public void testGameDefaultFrameRate_FlagOn() throws Exception { + mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_GAME_MODE); + + GameManagerService gameManagerService = Mockito.spy( + new GameManagerService(mMockContext, mTestLooper.getLooper(), + InstrumentationRegistry.getContext().getFilesDir(), + new Injector(){ + @Override + public GameManagerServiceSystemPropertiesWrapper + createSystemPropertiesWrapper() { + return mSysPropsMock; + } + })); + + when(mSysPropsMock.getInt( + ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE), + anyInt())).thenReturn(60); + when(mSysPropsMock.getBoolean( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + ArgumentMatchers.eq(true))).thenReturn(true); + gameManagerService.onBootCompleted(); + + // Set up a game in the foreground. + String[] packages = {mPackageName}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); + + // Toggle game default frame rate on. + gameManagerService.toggleGameDefaultFrameRate(true); + + // Verify that: + // 1) The system property is set correctly + // 2) setDefaultFrameRateOverride is called with correct arguments + Mockito.verify(mSysPropsMock).set( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + ArgumentMatchers.eq("true")); + Mockito.verify(gameManagerService, times(1)) + .setGameDefaultFrameRateOverride(ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), + ArgumentMatchers.eq(60.0f)); + + // Adding another game to the foreground. + String anotherGamePkg = "another.game"; + String[] packages2 = {anotherGamePkg}; + mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME); + int somePackageId = DEFAULT_PACKAGE_UID + 1; + when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); + + gameManagerService.mUidObserver.onUidStateChanged( + somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); + + // Toggle game default frame rate off. + when(mSysPropsMock.getBoolean( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + ArgumentMatchers.eq(true))).thenReturn(false); + gameManagerService.toggleGameDefaultFrameRate(false); + + // Verify that: + // 1) The system property is set correctly + // 2) setDefaultFrameRateOverride is called with correct arguments + Mockito.verify(mSysPropsMock).set( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + ArgumentMatchers.eq("false")); + Mockito.verify(gameManagerService).setGameDefaultFrameRateOverride( + ArgumentMatchers.eq(DEFAULT_PACKAGE_UID), ArgumentMatchers.eq(0.0f)); + Mockito.verify(gameManagerService).setGameDefaultFrameRateOverride( + ArgumentMatchers.eq(somePackageId), ArgumentMatchers.eq(0.0f)); + } + + @Test + public void testGameDefaultFrameRate_FlagOff() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_GAME_DEFAULT_FRAME_RATE); + mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_GAME_MODE); + + GameManagerService gameManagerService = Mockito.spy( + new GameManagerService(mMockContext, mTestLooper.getLooper(), + InstrumentationRegistry.getContext().getFilesDir(), + new Injector(){ + @Override + public GameManagerServiceSystemPropertiesWrapper + createSystemPropertiesWrapper() { + return mSysPropsMock; + } + })); + + // Set up a game in the foreground. + String[] packages = {mPackageName}; + when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); + gameManagerService.mUidObserver.onUidStateChanged( + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); + + // Toggle game default frame rate on. + when(mSysPropsMock.getInt( + ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE), + anyInt())).thenReturn(60); + when(mSysPropsMock.getBoolean( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + ArgumentMatchers.eq(true))).thenReturn(true); + + gameManagerService.toggleGameDefaultFrameRate(true); + + // Verify that: + // 1) System property is never set + // 2) setGameDefaultFrameRateOverride() should never be called if the flag is disabled. + Mockito.verify(mSysPropsMock, never()).set( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + anyString()); + Mockito.verify(gameManagerService, never()) + .setGameDefaultFrameRateOverride(anyInt(), anyFloat()); + + // Toggle game default frame rate off. + String anotherGamePkg = "another.game"; + String[] packages2 = {anotherGamePkg}; + mockAppCategory(anotherGamePkg, ApplicationInfo.CATEGORY_GAME); + int somePackageId = DEFAULT_PACKAGE_UID + 1; + when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); + gameManagerService.mUidObserver.onUidStateChanged( + somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); + gameManagerService.mUidObserver.onUidStateChanged( + somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + when(mSysPropsMock.getBoolean( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + ArgumentMatchers.eq(true))).thenReturn(false); + + gameManagerService.toggleGameDefaultFrameRate(false); + // Verify that: + // 1) System property is never set + // 2) setGameDefaultFrameRateOverride() should never be called if the flag is disabled. + Mockito.verify(mSysPropsMock, never()).set( + ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED), + anyString()); + Mockito.verify(gameManagerService, never()) + .setGameDefaultFrameRateOverride(anyInt(), anyFloat()); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java index 23886a17c890..00fe3d92413c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java @@ -25,6 +25,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -167,8 +168,12 @@ public class BackgroundJobsControllerTest { } private void setStoppedState(int uid, String pkgName, boolean stopped) { - doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid); - sendPackageStoppedBroadcast(uid, pkgName, stopped); + try { + doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid); + sendPackageStoppedBroadcast(uid, pkgName, stopped); + } catch (PackageManager.NameNotFoundException e) { + fail("Unable to set stopped state for unknown package: " + pkgName); + } } private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) { diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index a250ac75635b..07027645411d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -368,10 +368,15 @@ public class QuotaControllerTest { } } + private JobInfo.Builder createJobInfoBuilder(int jobId) { + return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestQuotaJobService")); + } + private JobStatus createJobStatus(String testTag, int jobId) { - JobInfo jobInfo = new JobInfo.Builder(jobId, - new ComponentName(mContext, "TestQuotaJobService")) - .build(); + return createJobStatus(testTag, createJobInfoBuilder(jobId).build()); + } + + private JobStatus createJobStatus(String testTag, JobInfo jobInfo) { return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo); } @@ -1333,39 +1338,70 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(0, SOURCE_PACKAGE, createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + final long timeUntilQuotaConsumedMs = 7 * MINUTE_IN_MILLIS; JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0); + //noinspection deprecation + JobStatus jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked", + createJobInfoBuilder(1) + .setImportantWhileForeground(true) + .setPriority(JobInfo.PRIORITY_DEFAULT) + .build()); + JobStatus jobHigh = createJobStatus("testGetMaxJobExecutionTimeLocked", + createJobInfoBuilder(2).setPriority(JobInfo.PRIORITY_HIGH).build()); setStandbyBucket(RARE_INDEX, job); + setStandbyBucket(RARE_INDEX, jobDefIWF); + setStandbyBucket(RARE_INDEX, jobHigh); setCharging(); synchronized (mQuotaController.mLock) { assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, mQuotaController.getMaxJobExecutionTimeMsLocked((job))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh))); } setDischarging(); setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); synchronized (mQuotaController.mLock) { - assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + assertEquals(timeUntilQuotaConsumedMs, mQuotaController.getMaxJobExecutionTimeMsLocked((job))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh))); } // Top-started job setProcessState(ActivityManager.PROCESS_STATE_TOP); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job, null); + trackJobs(job, jobDefIWF, jobHigh); mQuotaController.prepareForExecutionLocked(job); + mQuotaController.prepareForExecutionLocked(jobDefIWF); + mQuotaController.prepareForExecutionLocked(jobHigh); } setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); synchronized (mQuotaController.mLock) { - assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + assertEquals(timeUntilQuotaConsumedMs, mQuotaController.getMaxJobExecutionTimeMsLocked((job))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh))); mQuotaController.maybeStopTrackingJobLocked(job, null); + mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null); + mQuotaController.maybeStopTrackingJobLocked(jobHigh, null); } setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); synchronized (mQuotaController.mLock) { - assertEquals(7 * MINUTE_IN_MILLIS, + assertEquals(timeUntilQuotaConsumedMs, mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + assertEquals(timeUntilQuotaConsumedMs, + mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF)); + assertEquals(timeUntilQuotaConsumedMs, + mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh)); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java index de8b3080907c..c2b52b4ee9c8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java @@ -45,14 +45,15 @@ import android.os.Environment; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.parsing.PackageParser2; -import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import org.junit.Before; import org.junit.Rule; @@ -67,6 +68,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.Objects; +import java.util.Set; @SmallTest @Presubmit @@ -106,6 +108,18 @@ public class ApexManagerTest { public boolean hasFeature(String feature) { return true; } + + @androidx.annotation.NonNull + @Override + public Set<String> getHiddenApiWhitelistedApps() { + return new ArraySet<>(); + } + + @androidx.annotation.NonNull + @Override + public Set<String> getInstallConstraintsAllowlist() { + return new ArraySet<>(); + } }); mMockSystem.system().stageNominalSystemState(); @@ -385,7 +399,7 @@ public class ApexManagerTest { findFactory(results, "test.apex.rebootless").apexInfo); assertThat(factoryPkg.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath); assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1); - assertThat(AndroidPackageUtils.isSystem(factoryPkg)).isTrue(); + assertThat(AndroidPackageLegacyUtils.isSystem(factoryPkg)).isTrue(); } @Test @@ -416,7 +430,7 @@ public class ApexManagerTest { findFactory(results, "test.apex.rebootless").apexInfo); assertThat(factoryPkg.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath); assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1); - assertThat(AndroidPackageUtils.isSystem(factoryPkg)).isTrue(); + assertThat(AndroidPackageLegacyUtils.isSystem(factoryPkg)).isTrue(); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt index cf81f0a77702..e131a98b52d0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt @@ -16,12 +16,14 @@ package com.android.server.pm +import android.app.AppOpsManager import android.content.Intent import android.content.pm.PackageManager import android.os.Binder import com.android.server.testutils.any import com.android.server.testutils.eq import com.android.server.testutils.nullable +import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -31,6 +33,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify + @RunWith(JUnit4::class) class DistractingPackageHelperTest : PackageHelperTestBase() { @@ -40,6 +43,9 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { super.setup() distractingPackageHelper = DistractingPackageHelper( pms, broadcastHelper, suspendPackageHelper) + whenever(rule.mocks().appOpsManager.checkOpNoThrow( + eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any())) + .thenReturn(AppOpsManager.MODE_DEFAULT) } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 46806f334a27..7b29e2a4159d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -15,6 +15,7 @@ */ package com.android.server.pm +import android.app.AppOpsManager import android.app.PropertyInvalidatedCache import android.content.Context import android.content.Intent @@ -55,8 +56,10 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.spy import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder import com.android.internal.R +import com.android.internal.pm.parsing.pkg.PackageImpl import com.android.internal.pm.parsing.pkg.ParsedPackage import com.android.internal.pm.pkg.parsing.ParsingPackage +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils import com.android.server.LocalManagerRegistry import com.android.server.LocalServices import com.android.server.LockGuard @@ -67,10 +70,8 @@ import com.android.server.extendedtestutils.wheneverStatic import com.android.server.pm.dex.DexManager import com.android.server.pm.dex.DynamicCodeLogger import com.android.server.pm.parsing.PackageParser2 -import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.resolution.ComponentResolver import com.android.server.pm.snapshot.PackageDataSnapshot import com.android.server.pm.verify.domain.DomainVerificationManagerInternal @@ -80,14 +81,6 @@ import com.android.server.testutils.mock import com.android.server.testutils.nullable import com.android.server.testutils.whenever import com.android.server.utils.WatchedArrayMap -import libcore.util.HexEncoding -import org.junit.Assert -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement -import org.mockito.AdditionalMatchers.or -import org.mockito.Mockito -import org.mockito.quality.Strictness import java.io.File import java.io.IOException import java.nio.file.Files @@ -96,6 +89,14 @@ import java.security.cert.CertificateException import java.util.Arrays import java.util.Random import java.util.concurrent.FutureTask +import libcore.util.HexEncoding +import org.junit.Assert +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import org.mockito.AdditionalMatchers.or +import org.mockito.Mockito +import org.mockito.quality.Strictness /** * A utility for mocking behavior of the system and dependencies when testing PackageManagerService @@ -151,7 +152,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { .mockStatic(EventLog::class.java) .mockStatic(LocalServices::class.java) .mockStatic(LocalManagerRegistry::class.java) - .mockStatic(DeviceConfig::class.java) + .mockStatic(DeviceConfig::class.java, Mockito.CALLS_REAL_METHODS) .mockStatic(HexEncoding::class.java) .apply(withSession) session = apply.startMocking() @@ -192,6 +193,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { val userManagerService: UserManagerService = mock() val componentResolver: ComponentResolver = mock() val permissionManagerInternal: PermissionManagerServiceInternal = mock() + val appOpsManager: AppOpsManager = mock() val incrementalManager: IncrementalManager = mock() val platformCompat: PlatformCompat = mock() val settings: Settings = mock() @@ -304,6 +306,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider } whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler } whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper } + whenever(mocks.injector.getSystemService(AppOpsManager::class.java)) { mocks.appOpsManager } wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig) whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP) whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index 2332988cc832..e5ecdc478df7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -62,6 +62,7 @@ import android.os.ParcelableException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.text.TextUtils; +import android.util.SparseArray; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -174,6 +175,7 @@ public class PackageArchiverTest { mUserState = new PackageUserStateImpl().setInstalled(true); mPackageSetting.setUserState(mUserId, mUserState); when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState); + when(mPackageState.getUserStates()).thenReturn(new SparseArray<>()); when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); when(mContext.getSystemService(AppOpsManager.class)).thenReturn( @@ -343,7 +345,7 @@ public class PackageArchiverTest { @Test public void archiveApp_appOptedOutOfArchiving() { - when(mAppOpsManager.checkOp( + when(mAppOpsManager.checkOpNoThrow( eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED); @@ -430,7 +432,7 @@ public class PackageArchiverTest { @Test public void isAppArchivable_appOptedOutOfArchiving() throws PackageManager.NameNotFoundException { - when(mAppOpsManager.checkOp( + when(mAppOpsManager.checkOpNoThrow( eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED); @@ -551,22 +553,12 @@ public class PackageArchiverTest { when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( null); - Exception e = assertThrows( - ParcelableException.class, - () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)); - assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); - assertThat(e.getCause()).hasMessageThat().isEqualTo( - String.format("Package %s not found.", PACKAGE)); + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull(); } @Test public void getArchivedAppIcon_notArchived() { - Exception e = assertThrows( - ParcelableException.class, - () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)); - assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); - assertThat(e.getCause()).hasMessageThat().isEqualTo( - String.format("Package %s is not currently archived.", PACKAGE)); + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull(); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt index a6ba5d4c3032..7b80aea80035 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt @@ -36,6 +36,7 @@ import org.mockito.MockitoAnnotations open class PackageHelperTestBase { companion object { + const val PLATFORM_PACKAGE_NAME = "android" const val TEST_PACKAGE_1 = "com.android.test.package1" const val TEST_PACKAGE_2 = "com.android.test.package2" const val DEVICE_OWNER_PACKAGE = "com.android.test.owner" diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt index e685c3fa9fa4..944b1aae7bda 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -21,17 +21,17 @@ import android.content.pm.PackageManager import android.content.pm.SharedLibraryInfo import android.content.pm.VersionedPackage import android.os.Build -import android.os.storage.StorageManager import android.os.UserHandle +import android.os.storage.StorageManager import android.util.ArrayMap import android.util.PackageUtils +import com.android.internal.pm.parsing.pkg.PackageImpl import com.android.internal.pm.parsing.pkg.ParsedPackage import com.android.server.SystemConfig.SharedLibraryEntry import com.android.server.compat.PlatformCompat import com.android.server.extendedtestutils.wheneverStatic import com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.testutils.any import com.android.server.testutils.eq import com.android.server.testutils.mock diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt index 7b381ce443e1..ae53e707a7cc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -16,12 +16,14 @@ package com.android.server.pm +import android.app.AppOpsManager import android.content.Intent import android.content.pm.SuspendDialogInfo import android.os.Binder import android.os.PersistableBundle import com.android.server.testutils.any import com.android.server.testutils.eq +import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -32,6 +34,12 @@ import org.mockito.Mockito.verify @RunWith(JUnit4::class) class SuspendPackageHelperTest : PackageHelperTestBase() { + override fun setup() { + super.setup() + whenever(rule.mocks().appOpsManager.checkOpNoThrow( + eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any())) + .thenReturn(AppOpsManager.MODE_DEFAULT) + } @Test fun setPackagesSuspended() { @@ -275,6 +283,72 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { } @Test + fun getSuspendingPackagePrecedence() { + val launcherExtras = PersistableBundle() + launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) + val targetPackages = arrayOf(TEST_PACKAGE_2) + // Suspend. + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + targetPackages, true /* suspended */, null /* appExtras */, launcherExtras, + null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, + false /* quarantined */) + assertThat(failedNames).isEmpty() + testHandler.flush() + + assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + + // Suspend by system. + failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + targetPackages, true /* suspended */, null /* appExtras */, launcherExtras, + null /* dialogInfo */, PLATFORM_PACKAGE_NAME, TEST_USER_ID, deviceOwnerUid, + false /* quarantined */) + assertThat(failedNames).isEmpty() + testHandler.flush() + + assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME) + + // QAS by package1. + failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + targetPackages, true /* suspended */, null /* appExtras */, launcherExtras, + null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid, + true /* quarantined */) + assertThat(failedNames).isEmpty() + testHandler.flush() + + assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(TEST_PACKAGE_1) + + // Un-QAS by package1. + suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), + targetPackages, { suspendingPackage -> suspendingPackage == TEST_PACKAGE_1 }, + TEST_USER_ID) + testHandler.flush() + + assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME) + + // Un-suspend by system. + suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), + targetPackages, { suspendingPackage -> suspendingPackage == PLATFORM_PACKAGE_NAME }, + TEST_USER_ID) + testHandler.flush() + + assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) + + // Unsuspend. + suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), + targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, + TEST_USER_ID) + testHandler.flush() + + assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), + TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull() + } + + @Test fun getSuspendedDialogInfo() { val dialogInfo = SuspendDialogInfo.Builder() .setTitle(TEST_PACKAGE_1).build() diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index f02e5a5ece24..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,7 +26,7 @@ android_test { ], exclude_srcs: [ - "src/com/android/server/power/stats/PowerStatsStoreTest.java", + ":power_stats_ravenwood_tests", ], static_libs: [ @@ -62,13 +76,14 @@ android_ravenwood_test { static_libs: [ "services.core", "modules-utils-binary-xml", - "androidx.annotation_annotation", "androidx.test.rules", + "truth", + "mockito_ravenwood", ], srcs: [ - "src/com/android/server/power/stats/PowerStatsStoreTest.java", + ":power_stats_ravenwood_tests", + "src/com/android/server/power/stats/MockClock.java", ], - sdk_version: "test_current", 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 48e2dd74fcef..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 @@ -26,8 +26,6 @@ import android.os.BatteryConsumer; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.MultiStateStats; - import org.junit.Test; import org.junit.runner.RunWith; @@ -37,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/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java index 8ca4ff6f86f5..993d834b9500 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java @@ -38,7 +38,6 @@ import android.util.LongArray; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerStats; 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 eb03a6c14f7d..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 @@ -26,8 +26,6 @@ import android.os.BatteryConsumer; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.MultiStateStats; - import org.junit.Test; import org.junit.runner.RunWith; @@ -38,7 +36,6 @@ import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest public class MultiStateStatsTest { - 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/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml index 9b047f2abff7..6537d47106a9 100644 --- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml +++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml @@ -40,6 +40,7 @@ mediaSharedWithParent='true' credentialShareableWithParent='false' authAlwaysRequiredToDisableQuietMode='true' + allowStoppingUserWithDelayedLocking='true' showInSettings='23' hideInSettingsInQuietMode='true' inheritDevicePolicy='450' diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index a9967f63b3b3..71d64cf4c8d4 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -532,12 +532,11 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) - public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() { + public void testTwoFingerDoubleTap_StateIsIdle_shouldInActivated() { goFromStateIdleTo(STATE_IDLE); twoFingerTap(); twoFingerTap(); - twoFingerTap(); assertIn(STATE_ACTIVATED); verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); @@ -546,13 +545,12 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) - public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() { + public void testTwoFingerDoubleTap_StateIsActivated_shouldInIdle() { goFromStateIdleTo(STATE_ACTIVATED); reset(mMockMagnificationLogger); twoFingerTap(); twoFingerTap(); - twoFingerTap(); assertIn(STATE_IDLE); verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); @@ -561,11 +559,10 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) - public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() { + public void testTwoFingerDoubleTapAndHold_StateIsIdle_shouldZoomsImmediately() { goFromStateIdleTo(STATE_IDLE); twoFingerTap(); - twoFingerTap(); twoFingerTapAndHold(); assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); @@ -575,11 +572,10 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) - public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() { + public void testTwoFingerDoubleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() { goFromStateIdleTo(STATE_IDLE); twoFingerTap(); - twoFingerTap(); twoFingerSwipeAndHold(); assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java index a7cf361c7bc1..009bfb7efca6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java @@ -52,8 +52,8 @@ import android.provider.Settings; import android.test.mock.MockContentResolver; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.accessibility.IMagnificationConnectionCallback; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; import androidx.test.core.app.ApplicationProvider; @@ -146,7 +146,7 @@ public class MagnificationConnectionManagerTest { assertTrue(mMagnificationConnectionManager.isConnected()); verify(mMockConnection.asBinder()).linkToDeath(any(IBinder.DeathRecipient.class), eq(0)); verify(mMockConnection.getConnection()).setConnectionCallback( - any(IWindowMagnificationConnectionCallback.class)); + any(IMagnificationConnectionCallback.class)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java index 8fdd884380d5..07f3036410a0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java @@ -25,8 +25,8 @@ import android.os.RemoteException; import android.provider.Settings; import android.view.Display; import android.view.accessibility.IMagnificationConnection; +import android.view.accessibility.IMagnificationConnectionCallback; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; import com.android.server.accessibility.AccessibilityTraceManager; @@ -49,7 +49,7 @@ public class MagnificationConnectionWrapperTest { @Mock private AccessibilityTraceManager mTrace; @Mock - private IWindowMagnificationConnectionCallback mCallback; + private IMagnificationConnectionCallback mCallback; @Mock private MagnificationAnimationCallback mAnimationCallback; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java index 3d3d0b7aa07a..35b6c9085501 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java @@ -32,8 +32,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.view.Display; import android.view.accessibility.IMagnificationConnection; +import android.view.accessibility.IMagnificationConnectionCallback; import android.view.accessibility.IRemoteMagnificationAnimationCallback; -import android.view.accessibility.IWindowMagnificationConnectionCallback; import java.util.ArrayList; import java.util.List; @@ -53,7 +53,7 @@ class MockMagnificationConnection { private boolean mHasPendingCallback = false; private boolean mWindowMagnificationEnabled = false; private IBinder.DeathRecipient mDeathRecipient; - private IWindowMagnificationConnectionCallback mIMirrorWindowCallback; + private IMagnificationConnectionCallback mIMagnificationCallback; private Rect mMirrorWindowFrame = new Rect(0, 0, 500, 500); private float mScale = 2.0f; @@ -74,10 +74,10 @@ class MockMagnificationConnection { mBinder = mock(Binder.class); when(mConnection.asBinder()).thenReturn(mBinder); doAnswer((invocation) -> { - mIMirrorWindowCallback = invocation.getArgument(0); + mIMagnificationCallback = invocation.getArgument(0); return null; }).when(mConnection).setConnectionCallback( - any(IWindowMagnificationConnectionCallback.class)); + any(IMagnificationConnectionCallback.class)); doAnswer((invocation) -> { mDeathRecipient = invocation.getArgument(0); @@ -166,8 +166,8 @@ class MockMagnificationConnection { return mDeathRecipient; } - IWindowMagnificationConnectionCallback getConnectionCallback() { - return mIMirrorWindowCallback; + IMagnificationConnectionCallback getConnectionCallback() { + return mIMagnificationCallback; } Rect getMirrorWindowFrame() { @@ -185,10 +185,10 @@ class MockMagnificationConnection { if (!mHasPendingCallback) { throw new IllegalStateException("There is no any pending callbacks"); } - if (mWindowMagnificationEnabled && mIMirrorWindowCallback != null) { - mIMirrorWindowCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY, + if (mWindowMagnificationEnabled && mIMagnificationCallback != null) { + mIMagnificationCallback.onWindowMagnifierBoundsChanged(TEST_DISPLAY, mMirrorWindowFrame); - mIMirrorWindowCallback.onSourceBoundsChanged(TEST_DISPLAY, + mIMagnificationCallback.onSourceBoundsChanged(TEST_DISPLAY, mSourceBounds); } sendAnimationEndCallbackIfNeeded(success); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index a3b67aef551a..c99e04037023 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -72,15 +72,15 @@ public class WindowMagnificationGestureHandlerTest { public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4; public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 5; public static final int STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 6; - public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP = 7; - public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 8; - public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 9; + public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP = 7; + public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 8; + public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 9; //TODO: Test it after can injecting Handler to GestureMatcher is available. public static final int FIRST_STATE = STATE_IDLE; public static final int LAST_STATE = STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD; public static final int LAST_STATE_WITH_MULTI_FINGER = - STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD; + STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD; // Co-prime x and y, to potentially catch x-y-swapped errors public static final float DEFAULT_TAP_X = 301; @@ -257,15 +257,15 @@ public class WindowMagnificationGestureHandlerTest { break; case STATE_SHOW_MAGNIFIER_SHORTCUT: case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: - case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: + case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: check(isWindowMagnifierEnabled(DISPLAY_0), state); check(mWindowMagnificationGestureHandler.mCurrentState == mWindowMagnificationGestureHandler.mDetectingState, state); break; case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: - case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: - case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: { check(isWindowMagnifierEnabled(DISPLAY_0), state); check(mWindowMagnificationGestureHandler.mCurrentState == mWindowMagnificationGestureHandler.mViewportDraggingState, state); @@ -337,8 +337,7 @@ public class WindowMagnificationGestureHandlerTest { tapAndHold(); } break; - case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: { - twoFingerTap(); + case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: { twoFingerTap(); twoFingerTap(); // Wait for two-finger tap gesture completed. @@ -346,17 +345,15 @@ public class WindowMagnificationGestureHandlerTest { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } break; - case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { - twoFingerTap(); + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: { twoFingerTap(); twoFingerTapAndHold(); } break; - case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: { // enabled then perform two finger triple tap and hold gesture goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT); twoFingerTap(); - twoFingerTap(); twoFingerTapAndHold(); } break; @@ -394,16 +391,15 @@ public class WindowMagnificationGestureHandlerTest { } break; case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: - case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); break; case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: - case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); returnToNormalFrom(STATE_SHOW_MAGNIFIER_SHORTCUT); break; - case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: { - twoFingerTap(); + case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: { twoFingerTap(); twoFingerTap(); // Wait for two-finger tap gesture completed. diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index d26d67107001..77b1455a2ecc 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -92,6 +92,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Log; import android.view.Display; @@ -104,11 +105,14 @@ import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserJourneyLogger; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; +import com.android.server.pm.UserTypeDetails; +import com.android.server.pm.UserTypeFactory; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerService; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -175,6 +179,9 @@ public class UserControllerTest { USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() throws Exception { runWithDexmakerShareClassLoader(() -> { @@ -789,28 +796,99 @@ public class UserControllerTest { } @Test - public void testStartProfile() throws Exception { - setUpAndStartProfileInBackground(TEST_USER_ID1); + public void testStartManagedProfile() throws Exception { + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); startBackgroundUserAssertions(); verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY); } @Test - public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception { + public void testStartManagedProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception { mockIsUsersOnSecondaryDisplaysEnabled(true); - setUpAndStartProfileInBackground(TEST_USER_ID1); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); startBackgroundUserAssertions(); verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY); } @Test - public void testStopProfile() throws Exception { - setUpAndStartProfileInBackground(TEST_USER_ID1); + public void testStopManagedProfile() throws Exception { + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); + assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true); + verifyUserUnassignedFromDisplay(TEST_USER_ID1); + } + + @Test + public void testStopPrivateProfile() throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true); verifyUserUnassignedFromDisplay(TEST_USER_ID1); + + mSetFlagsRule.disableFlags( + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* expectLocking= */ true); + verifyUserUnassignedFromDisplay(TEST_USER_ID2); + } + + @Test + public void testStopPrivateProfileWithDelayedLocking() throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ false); + } + + @Test + public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.disableFlags( + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); + + mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); + } + + @Test + public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached() + throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); + setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); + } + + @Test + public void testStopManagedProfileWithDelayedLocking() throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); } /** Tests handleIncomingUser() for a variety of permissions and situations. */ @@ -1001,8 +1079,8 @@ public class UserControllerTest { mUserStates.put(userId, mUserController.getStartedUserState(userId)); } - private void setUpAndStartProfileInBackground(int userId) throws Exception { - setUpUser(userId, UserInfo.FLAG_PROFILE, false, UserManager.USER_TYPE_PROFILE_MANAGED); + private void setUpAndStartProfileInBackground(int userId, String userType) throws Exception { + setUpUser(userId, UserInfo.FLAG_PROFILE, false, userType); assertThat(mUserController.startProfile(userId, /* evenWhenDisabled=*/ false, /* unlockListener= */ null)).isTrue(); @@ -1070,6 +1148,11 @@ public class UserControllerTest { userInfo.preCreated = preCreated; when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); when(mInjector.mUserManagerMock.isPreCreated(userId)).thenReturn(preCreated); + + UserTypeDetails userTypeDetails = UserTypeFactory.getUserTypes().get(userType); + assertThat(userTypeDetails).isNotNull(); + when(mInjector.mUserManagerInternalMock.getUserProperties(eq(userId))) + .thenReturn(userTypeDetails.getDefaultUserPropertiesReference()); } private static List<String> getActions(List<Intent> intents) { diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index cc3c880b8927..f1c1dc365b90 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -266,18 +266,22 @@ public class AudioDeviceBrokerTest { .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED); // no metadata set - assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, - DEVICE_TYPE_DEFAULT.getBytes())); - assertFalse( - mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed( - mFakeBtDevice.getAddress())); + if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_DEFAULT.getBytes())) { + assertFalse(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed( + mFakeBtDevice.getAddress())); + } // metadata set - assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, - DEVICE_TYPE_HEADSET.getBytes())); - assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed( - mFakeBtDevice.getAddress())); + if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_HEADSET.getBytes())) { + assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed( + mFakeBtDevice.getAddress())); + } } finally { + // reset the metadata device type + mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_DEFAULT.getBytes()); InstrumentationRegistry.getInstrumentation().getUiAutomation() .dropShellPermissionIdentity(); } @@ -304,25 +308,30 @@ public class AudioDeviceBrokerTest { .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED); // no metadata set - assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, - DEVICE_TYPE_DEFAULT.getBytes())); - assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER, - mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress( - mFakeBtDevice.getAddress())); - verify(mMockAudioService, - timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState( - eq(devState)); + if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_DEFAULT.getBytes())) { + assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER, + mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress( + mFakeBtDevice.getAddress())); + verify(mMockAudioService, + timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState( + eq(devState)); + } // metadata set - assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, - DEVICE_TYPE_HEADSET.getBytes())); - assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES, - mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress( - mFakeBtDevice.getAddress())); - verify(mMockAudioService, - timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState( - any()); + if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_HEADSET.getBytes())) { + assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES, + mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress( + mFakeBtDevice.getAddress())); + verify(mMockAudioService, + timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState( + any()); + } } finally { + // reset the metadata device type + mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_DEFAULT.getBytes()); InstrumentationRegistry.getInstrumentation().getUiAutomation() .dropShellPermissionIdentity(); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java index 33559107dfbb..18e6f0a2cc57 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java @@ -151,6 +151,19 @@ public class GenericWindowPolicyControllerTest { } @Test + public void userNotAllowlisted_launchIsBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithNoAllowedUsers(); + gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() { GenericWindowPolicyController gwpc = createGwpc(); gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false); @@ -702,6 +715,26 @@ public class GenericWindowPolicyControllerTest { /* customHomeComponent= */ null); } + private GenericWindowPolicyController createGwpcWithNoAllowedUsers() { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ new ArraySet<>(), + /* crossTaskNavigationAllowedByDefault= */ true, + /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ mSecureWindowCallback, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + private GenericWindowPolicyController createGwpcWithCustomHomeComponent( ComponentName homeComponent) { return new GenericWindowPolicyController( diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java index fe37f4241d8e..b3d25f2eef25 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -47,6 +47,10 @@ import java.util.Map; @Presubmit @RunWith(AndroidJUnit4.class) public final class OverrideRequestControllerTest { + + private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0); + private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0); + private TestStatusChangeListener mStatusListener; private OverrideRequestController mController; @@ -59,7 +63,7 @@ public final class OverrideRequestControllerTest { @Test public void addRequest() { OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(request)); mController.addRequest(request); @@ -69,14 +73,14 @@ public final class OverrideRequestControllerTest { @Test public void addRequest_cancelExistingRequestThroughNewRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(firstRequest)); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); assertNull(mStatusListener.getLastStatus(secondRequest)); mController.addRequest(secondRequest); @@ -87,7 +91,7 @@ public final class OverrideRequestControllerTest { @Test public void addRequest_cancelActiveRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mController.addRequest(firstRequest); @@ -101,7 +105,7 @@ public final class OverrideRequestControllerTest { @Test public void addBaseStateRequest() { OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); assertNull(mStatusListener.getLastStatus(request)); mController.addBaseStateRequest(request); @@ -111,14 +115,14 @@ public final class OverrideRequestControllerTest { @Test public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); assertNull(mStatusListener.getLastStatus(firstRequest)); mController.addBaseStateRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); assertNull(mStatusListener.getLastStatus(secondRequest)); mController.addBaseStateRequest(secondRequest); @@ -129,7 +133,7 @@ public final class OverrideRequestControllerTest { @Test public void addBaseStateRequest_cancelActiveBaseStateRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addBaseStateRequest(firstRequest); @@ -143,13 +147,13 @@ public final class OverrideRequestControllerTest { @Test public void handleBaseStateChanged() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, + TEST_DEVICE_STATE_ZERO, DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); @@ -169,11 +173,11 @@ public final class OverrideRequestControllerTest { @Test public void handleProcessDied() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); @@ -192,11 +196,11 @@ public final class OverrideRequestControllerTest { mController.setStickyRequestsAllowed(true); OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); @@ -215,11 +219,11 @@ public final class OverrideRequestControllerTest { @Test public void handleNewSupportedStates() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE); mController.addRequest(firstRequest); @@ -242,7 +246,7 @@ public final class OverrideRequestControllerTest { @Test public void cancelOverrideRequestsTest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */, - 1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); + TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); mController.addRequest(firstRequest); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index a2a8424881d4..0973d46283ed 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -23,6 +23,7 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2; import static com.android.server.hdmi.Constants.ADDR_RECORDER_1; import static com.android.server.hdmi.Constants.ADDR_TV; +import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.google.common.truth.Truth.assertThat; @@ -1712,13 +1713,14 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + assertThat(mHdmiControlService.getLocalActiveSource()).isEqualTo( + new ActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS)); mNativeWrapper.clearResultMessages(); mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); mTestLooper.dispatchAll(); @@ -1728,8 +1730,64 @@ public class HdmiCecLocalDeviceTvTest { mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv); + assertThat(mHdmiControlService.getLocalActiveSource()).isEqualTo( + new ActiveSource(mTvLogicalAddress, mTvPhysicalAddress)); + } + + @Test + public void requestActiveSourceActionComplete_validLocalActiveSource_doNotSendActiveSource() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mHdmiControlService.setActiveSource(mTvLogicalAddress, mTvPhysicalAddress, + "HdmiCecLocalDeviceTvTest"); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); + } + + @Test + public void onAddressAllocated_startRequestActiveSourceAction_cancelOnDeviceSelect() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_PLAYBACK_1) + .setPhysicalAddress(0x1000) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 1") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null); + mTestLooper.dispatchAll(); + + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); + mTestLooper.dispatchAll(); + + // RequestActiveSourceAction should be cancelled and TV will not broadcast <Active Source>. + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); } + @Test public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() { mHdmiControlService.getHdmiCecNetwork().clearDeviceList(); @@ -1737,7 +1795,7 @@ public class HdmiCecLocalDeviceTvTest { .isEmpty(); HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( - ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK); + ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK); HdmiCecMessage giveOsdName = HdmiCecMessageBuilder.buildGiveOsdNameCommand( ADDR_TV, ADDR_PLAYBACK_2); mNativeWrapper.onCecMessage(reportPhysicalAddress); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index 18961c0feef9..ee076c6bcf4b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -140,9 +140,9 @@ public class LockSettingsServiceTestable extends LockSettingsService { } @Override - public ManagedProfilePasswordCache getManagedProfilePasswordCache( + public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache( java.security.KeyStore ks) { - return mock(ManagedProfilePasswordCache.class); + return mock(UnifiedProfilePasswordCache.class); } @Override diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java index dd687fd4286c..86a1358f371c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java @@ -27,9 +27,9 @@ import static org.mockito.Mockito.when; import android.os.Build; import android.platform.test.annotations.Presubmit; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.compat.PlatformCompat; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.PackageState; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java index 72cc969b5fb1..d7ed7c2d6469 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java @@ -69,6 +69,7 @@ public class UserManagerServiceUserPropertiesTest { .setMediaSharedWithParent(false) .setCredentialShareableWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(false) + .setAllowStoppingUserWithDelayedLocking(false) .setDeleteAppWithParent(false) .setAlwaysVisible(false) .setCrossProfileContentSharingStrategy(0) @@ -85,6 +86,7 @@ public class UserManagerServiceUserPropertiesTest { actualProps.setMediaSharedWithParent(true); actualProps.setCredentialShareableWithParent(false); actualProps.setAuthAlwaysRequiredToDisableQuietMode(true); + actualProps.setAllowStoppingUserWithDelayedLocking(true); actualProps.setDeleteAppWithParent(true); actualProps.setAlwaysVisible(true); actualProps.setCrossProfileContentSharingStrategy(1); @@ -130,6 +132,7 @@ public class UserManagerServiceUserPropertiesTest { .setMediaSharedWithParent(true) .setDeleteAppWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(false) + .setAllowStoppingUserWithDelayedLocking(false) .setAlwaysVisible(true) .build(); final UserProperties orig = new UserProperties(defaultProps); @@ -139,6 +142,7 @@ public class UserManagerServiceUserPropertiesTest { orig.setInheritDevicePolicy(9456); orig.setDeleteAppWithParent(false); orig.setAuthAlwaysRequiredToDisableQuietMode(true); + orig.setAllowStoppingUserWithDelayedLocking(true); orig.setAlwaysVisible(false); // Test every permission level. (Currently, it's linear so it's easy.) @@ -184,6 +188,8 @@ public class UserManagerServiceUserPropertiesTest { assertEqualGetterOrThrows(orig::getDeleteAppWithParent, copy::getDeleteAppWithParent, exposeAll); assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll); + assertEqualGetterOrThrows(orig::getAllowStoppingUserWithDelayedLocking, + copy::getAllowStoppingUserWithDelayedLocking, exposeAll); // Items requiring hasManagePermission - put them here using hasManagePermission. assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings, @@ -258,6 +264,8 @@ public class UserManagerServiceUserPropertiesTest { .isEqualTo(actual.isCredentialShareableWithParent()); assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode()) .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode()); + assertThat(expected.getAllowStoppingUserWithDelayedLocking()) + .isEqualTo(actual.getAllowStoppingUserWithDelayedLocking()); assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent()); assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible()); assertThat(expected.getCrossProfileContentSharingStrategy()) diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index d0ad57365942..70837061b0bb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -90,6 +90,7 @@ public class UserManagerServiceUserTypeTest { .setMediaSharedWithParent(true) .setCredentialShareableWithParent(false) .setAuthAlwaysRequiredToDisableQuietMode(true) + .setAllowStoppingUserWithDelayedLocking(true) .setShowInSettings(900) .setShowInSharingSurfaces(20) .setShowInQuietMode(30) @@ -167,6 +168,8 @@ public class UserManagerServiceUserTypeTest { assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent()); assertTrue(type.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); + assertTrue(type.getDefaultUserPropertiesReference() + .getAllowStoppingUserWithDelayedLocking()); assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings()); assertEquals(20, type.getDefaultUserPropertiesReference().getShowInSharingSurfaces()); assertEquals(30, @@ -322,6 +325,7 @@ public class UserManagerServiceUserTypeTest { .setMediaSharedWithParent(false) .setCredentialShareableWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(false) + .setAllowStoppingUserWithDelayedLocking(false) .setShowInSettings(20) .setInheritDevicePolicy(21) .setShowInSharingSurfaces(22) @@ -367,6 +371,8 @@ public class UserManagerServiceUserTypeTest { .isCredentialShareableWithParent()); assertFalse(aospType.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); + assertFalse(aospType.getDefaultUserPropertiesReference() + .getAllowStoppingUserWithDelayedLocking()); assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings()); assertEquals(21, aospType.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); @@ -420,6 +426,8 @@ public class UserManagerServiceUserTypeTest { .isCredentialShareableWithParent()); assertTrue(aospType.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); + assertTrue(aospType.getDefaultUserPropertiesReference() + .getAllowStoppingUserWithDelayedLocking()); assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings()); assertEquals(22, aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces()); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index ced0bb5bc51c..a743fff5d2ea 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -342,8 +342,12 @@ public final class UserManagerTest { assertThat(typeProps.getCrossProfileContentSharingStrategy()) .isEqualTo(privateProfileUserProperties.getCrossProfileContentSharingStrategy()); assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent); + assertThrows(SecurityException.class, + privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking); + compareDrawables(mUserManager.getUserBadge(), Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain())); + // Verify private profile parent assertThat(mUserManager.getProfileParent(mainUserId)).isNull(); UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java index 8464969cd0f3..ee93bc1e3562 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java @@ -53,10 +53,10 @@ import androidx.test.filters.MediumTest; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.server.LocalServices; import com.android.server.SystemConfig; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import org.junit.After; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index 0f87202f8939..587f5fa9c623 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -29,9 +29,9 @@ import android.util.SparseArray; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.pkg.parsing.ParsingPackage; -import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.AndroidPackage; import dalvik.system.DelegateLastClassLoader; diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt index ee23a00f27d7..9b4ca2a86f2c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt @@ -21,17 +21,17 @@ import android.os.Environment import android.os.SystemProperties.PROP_VALUE_MAX import android.platform.test.annotations.Postsubmit import com.android.internal.R +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.PackageManagerService -import com.android.server.pm.pkg.parsing.ParsingPackageUtils import com.google.common.truth.Truth.assertThat +import java.io.ByteArrayInputStream +import java.io.File import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows import org.junit.Assert.fail import org.junit.Test import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserFactory -import java.io.ByteArrayInputStream -import java.io.File @Postsubmit class AndroidPackageParsingValidationTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt index 2332817911f7..c44f583a93ef 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt @@ -17,6 +17,7 @@ package com.android.server.pm.parsing import android.content.pm.ApplicationInfo +import android.util.ArraySet import java.io.File class TestPackageParser2(var cacheDir: File? = null) : PackageParser2( @@ -33,4 +34,7 @@ class TestPackageParser2(var cacheDir: File? = null) : PackageParser2( // behavior. return false } + + override fun getHiddenApiWhitelistedApps() = ArraySet<String>() + override fun getInstallConstraintsAllowlist() = ArraySet<String>() }) diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index 5ba4851270fd..759b204516c6 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -32,8 +32,9 @@ import android.content.rollback.PackageRollbackInfo; import android.util.SparseArray; import android.util.SparseIntArray; +import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.server.pm.PackageList; -import com.android.server.pm.parsing.pkg.PackageImpl; + import com.google.common.collect.Range; import org.junit.Before; @@ -415,7 +416,7 @@ public class RollbackUnitTest { private void addPkgWithMinExtVersions(String pkg, int[][] minExtVersions) { mPackages.add(pkg); - PackageImpl pkgImpl = new PackageImpl(pkg, "baseCodePath", "codePath", null, false); + PackageImpl pkgImpl = new PackageImpl(pkg, "baseCodePath", "codePath", null, false, null); pkgImpl.setMinExtensionVersions(sparseArrayFrom(minExtVersions)); when(mMockPmi.getPackage(pkg)).thenReturn(pkgImpl); diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java index 330dbb83e949..861d14a2cf66 100644 --- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java @@ -30,13 +30,19 @@ import androidx.test.filters.SmallTest; import com.android.internal.annotations.GuardedBy; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest @Presubmit +@RunWith(Parameterized.class) public class AnrTimerTest { // The commonly used message timeout key. @@ -106,6 +112,16 @@ public class AnrTimerTest { } /** + * Force AnrTimer to use the test parameter for the feature flag. + */ + class TestInjector extends AnrTimer.Injector { + @Override + boolean anrTimerServiceEnabled() { + return mEnabled; + } + } + + /** * An instrumented AnrTimer. */ private static class TestAnrTimer extends AnrTimer<TestArg> { @@ -137,6 +153,17 @@ public class AnrTimerTest { assertEquals(actual.what, MSG_TIMEOUT); } + @Parameters(name = "featureEnabled={0}") + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { {false}, {true} }); + } + + /** True if the feature is enabled. */ + private boolean mEnabled; + + public AnrTimerTest(boolean featureEnabled) { + mEnabled = featureEnabled; + } /** * Verify that a simple expiration succeeds. The timer is started for 10ms. The test diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index 8891413dd964..4e1c72af2727 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -40,11 +40,13 @@ android_test { "servicestests-utils", "testables", "truth", + "TestParameterInjector", // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", "flag-junit", "notification_flags_lib", + "platform-test-rules", ], libs: [ @@ -82,4 +84,7 @@ android_test { "libutils", "netd_aidl_interface-V5-cpp", ], + + // Required for TestParameterInjector + javacflags: ["-parameters"], } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java index 5febd02a75a8..260ee39656ea 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java @@ -16,30 +16,51 @@ package com.android.server.notification; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.app.UiModeManager; import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.display.ColorDisplayManager; import android.os.PowerManager; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.ZenDeviceEffects; +import android.service.notification.ZenModeConfig; import android.testing.TestableContext; import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidJUnit4.class) +@RunWith(TestParameterInjector.class) public class DefaultDeviceEffectsApplierTest { @Rule @@ -52,10 +73,31 @@ public class DefaultDeviceEffectsApplierTest { @Mock UiModeManager mUiModeManager; @Mock WallpaperManager mWallpaperManager; + private enum ChangeOrigin { + ORIGIN_UNKNOWN(ZenModeConfig.UPDATE_ORIGIN_UNKNOWN), + ORIGIN_INIT(ZenModeConfig.UPDATE_ORIGIN_INIT), + ORIGIN_INIT_USER(ZenModeConfig.UPDATE_ORIGIN_INIT_USER), + ORIGIN_USER(ZenModeConfig.UPDATE_ORIGIN_USER), + ORIGIN_APP(ZenModeConfig.UPDATE_ORIGIN_APP), + ORIGIN_SYSTEM_OR_SYSTEMUI(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), + ORIGIN_RESTORE_BACKUP(ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP); + + private final int mValue; + + ChangeOrigin(@ZenModeConfig.ConfigChangeOrigin int value) { + mValue = value; + } + + @ZenModeConfig.ConfigChangeOrigin + public int value() { + return mValue; + } + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = new TestableContext(InstrumentationRegistry.getContext(), null); + mContext = spy(new TestableContext(InstrumentationRegistry.getContext(), null)); mContext.addMockSystemService(PowerManager.class, mPowerManager); mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager); mContext.addMockSystemService(UiModeManager.class, mUiModeManager); @@ -74,25 +116,33 @@ public class DefaultDeviceEffectsApplierTest { .setShouldDisplayGrayscale(true) .setShouldUseNightMode(true) .build(); - mApplier.apply(effects); + mApplier.apply(effects, UPDATE_ORIGIN_USER); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); verify(mColorDisplayManager).setSaturationLevel(eq(0)); verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); - verifyZeroInteractions(mUiModeManager); // Coming later; adding now so test fails then. :) + verify(mUiModeManager).setNightModeActivatedForCustomMode( + eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); } @Test - public void apply_removesEffects() { + public void apply_removesPreviouslyAppliedEffects() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder() + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .build(); + mApplier.apply(previousEffects, UPDATE_ORIGIN_USER); + verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); + ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build(); - mApplier.apply(noEffects); + mApplier.apply(noEffects, UPDATE_ORIGIN_USER); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false)); - verify(mColorDisplayManager).setSaturationLevel(eq(100)); verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); - verifyZeroInteractions(mUiModeManager); + verifyZeroInteractions(mColorDisplayManager, mUiModeManager); } @Test @@ -107,9 +157,125 @@ public class DefaultDeviceEffectsApplierTest { .setShouldDisplayGrayscale(true) .setShouldUseNightMode(true) .build(); - mApplier.apply(effects); + mApplier.apply(effects, UPDATE_ORIGIN_USER); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); // (And no crash from missing services). } + + @Test + public void apply_someEffects_onlyThoseEffectsApplied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + ZenDeviceEffects effects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisplayGrayscale(true) + .build(); + mApplier.apply(effects, UPDATE_ORIGIN_USER); + + verify(mColorDisplayManager).setSaturationLevel(eq(0)); + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); + + verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean()); + verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean()); + } + + @Test + public void apply_onlyEffectDeltaApplied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(), + UPDATE_ORIGIN_USER); + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); + + // Apply a second effect and remove the first one. + mApplier.apply(new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build(), + UPDATE_ORIGIN_USER); + + // Wallpaper dimming was undone, Grayscale was applied, nothing else was touched. + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); + verify(mColorDisplayManager).setSaturationLevel(eq(0)); + verifyZeroInteractions(mPowerManager); + verifyZeroInteractions(mUiModeManager); + } + + @Test + public void apply_nightModeFromApp_appliedOnScreenOff() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + ArgumentCaptor<IntentFilter> intentFilterCaptor = + ArgumentCaptor.forClass(IntentFilter.class); + + when(mPowerManager.isInteractive()).thenReturn(true); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), + UPDATE_ORIGIN_APP); + + // Effect was not yet applied, but a broadcast receiver was registered. + verifyZeroInteractions(mUiModeManager); + verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), + intentFilterCaptor.capture(), anyInt()); + assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF); + BroadcastReceiver screenOffReceiver = broadcastReceiverCaptor.getValue(); + + // Now the "screen off" event comes. + screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + + // So the effect is applied, and we stopped listening for this event. + verify(mUiModeManager).setNightModeActivatedForCustomMode( + eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mContext).unregisterReceiver(eq(screenOffReceiver)); + } + + @Test + public void apply_nightModeWithScreenOff_appliedImmediately( + @TestParameter ChangeOrigin origin) { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + when(mPowerManager.isInteractive()).thenReturn(false); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), + origin.value()); + + // Effect was applied, and no broadcast receiver was registered. + verify(mUiModeManager).setNightModeActivatedForCustomMode( + eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mContext, never()).registerReceiver(any(), any(), anyInt()); + } + + @Test + @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}", + "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}"}) + public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(ChangeOrigin origin) { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + when(mPowerManager.isInteractive()).thenReturn(true); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), + origin.value()); + + // Effect was applied, and no broadcast receiver was registered. + verify(mUiModeManager).setNightModeActivatedForCustomMode( + eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mContext, never()).registerReceiver(any(), any(), anyInt()); + } + + @Test + @TestParameters({"{origin: ORIGIN_APP}", "{origin: ORIGIN_RESTORE_BACKUP}", + "{origin: ORIGIN_UNKNOWN}"}) + public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(ChangeOrigin origin) { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + when(mPowerManager.isInteractive()).thenReturn(true); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), + origin.value()); + + // Effect was not applied, will be on next screen-off. + verifyZeroInteractions(mUiModeManager); + verify(mContext).registerReceiver(any(), + argThat(filter -> Intent.ACTION_SCREEN_OFF.equals(filter.getAction(0))), + anyInt()); + } } 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 ee08fd26d631..14b551ae0b22 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -209,7 +209,11 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.permission.PermissionManager; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.platform.test.rule.DeniedDevices; +import android.platform.test.rule.DeviceProduct; +import android.platform.test.rule.LimitDevicesRule; import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; @@ -220,6 +224,7 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; +import android.service.notification.ZenModeConfig; import android.service.notification.ZenPolicy; import android.telecom.TelecomManager; import android.telephony.TelephonyManager; @@ -279,6 +284,7 @@ import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -311,6 +317,7 @@ import java.util.function.Consumer; @RunWith(AndroidTestingRunner.class) @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. @RunWithLooper +@DeniedDevices(denied = {DeviceProduct.CF_AUTO}) public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package"; @@ -329,6 +336,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private final int mUid = Binder.getCallingUid(); private final @UserIdInt int mUserId = UserHandle.getUserId(mUid); + @ClassRule + public static final LimitDevicesRule sLimitDevicesRule = new LimitDevicesRule(); + @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); @@ -970,6 +980,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return new NotificationRecord(mContext, sbn, channel); } + private NotificationRecord generateNotificationRecord(NotificationChannel channel, + long postTime) { + final StatusBarNotification sbn = generateSbn(PKG, mUid, postTime, 0); + return new NotificationRecord(mContext, sbn, channel); + } + private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) { return generateNotificationRecord(channel, 1, userId); } @@ -8929,8 +8945,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.addAutomaticZenRule(rule, "com.android.settings"); // verify that zen mode helper gets passed in a package name of "android" - verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(), - anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call + verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), + eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt()); } @Test @@ -8951,8 +8967,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.addAutomaticZenRule(rule, "com.android.settings"); // verify that zen mode helper gets passed in a package name of "android" - verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(), - anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call + verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), + eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt()); } @Test @@ -8972,8 +8988,44 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // verify that zen mode helper gets passed in the package name from the arg, not the owner verify(mockZenModeHelper).addAutomaticZenRule( - eq("another.package"), eq(rule), anyString(), anyInt(), - eq(ZenModeHelper.FROM_APP)); // doesn't count as a system/systemui call + eq("another.package"), eq(rule), eq(ZenModeConfig.UPDATE_ORIGIN_APP), + anyString(), anyInt()); // doesn't count as a system/systemui call + } + + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void testAddAutomaticZenRule_typeManagedCanBeUsedByDeviceOwners() throws Exception { + mService.setCallerIsNormalPackage(); + mService.setZenHelper(mock(ZenModeHelper.class)); + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + + AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")) + .setType(AutomaticZenRule.TYPE_MANAGED) + .setOwner(new ComponentName("pkg", "cls")) + .build(); + when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(true); + + mBinderService.addAutomaticZenRule(rule, "pkg"); + // No exception! + } + + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void testAddAutomaticZenRule_typeManagedCannotBeUsedByRegularApps() throws Exception { + mService.setCallerIsNormalPackage(); + mService.setZenHelper(mock(ZenModeHelper.class)); + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + + AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")) + .setType(AutomaticZenRule.TYPE_MANAGED) + .setOwner(new ComponentName("pkg", "cls")) + .build(); + when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(false); + + assertThrows(IllegalArgumentException.class, + () -> mBinderService.addAutomaticZenRule(rule, "pkg")); } @Test @@ -13185,7 +13237,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy); - verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); } @Test @@ -13207,7 +13259,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy); - verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); } @Test @@ -13223,7 +13275,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy); - verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); } @Test @@ -13271,7 +13323,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY); verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), - eq("package"), anyString(), anyInt(), anyBoolean()); + eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"), + anyInt()); } @Test @@ -13280,6 +13333,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenModeHelper; + mService.setCallerIsNormalPackage(); when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) .thenReturn(true); when(mCompanionMgr.getAssociations(anyString(), anyInt())) @@ -13292,7 +13346,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY); verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), - eq("package"), anyString(), anyInt(), anyBoolean()); + eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt()); } @Test @@ -13309,6 +13363,175 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(n.flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(0); } + @Test + public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne() + throws RemoteException { + mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags + .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); + + // Create recent notification. + final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, + System.currentTimeMillis()); + mService.addNotification(nr1); + + // Create old notification. + final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); + mService.addNotification(nr2); + + // Cancel specific notifications via listener. + String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + + // Notifications should not be active anymore. + StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); + assertThat(notifications).isEmpty(); + assertEquals(0, mService.getNotificationRecordCount()); + // Ensure cancel event is logged. + verify(mAppOpsManager).noteOpNoThrow( + AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null, null); + } + + @Test + public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException { + mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags + .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); + + // Create old notifications. + final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0); + mService.addNotification(nr1); + + final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); + mService.addNotification(nr2); + + // Cancel specific notifications via listener. + String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + + // Notifications should not be active anymore. + StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); + assertThat(notifications).isEmpty(); + assertEquals(0, mService.getNotificationRecordCount()); + // Ensure cancel event is not logged. + verify(mAppOpsManager, never()).noteOpNoThrow( + eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), + any(), any()); + } + + @Test + public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled() + throws RemoteException { + mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags + .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); + + // Create recent notification. + final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, + System.currentTimeMillis()); + mService.addNotification(nr1); + + // Create old notification. + final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); + mService.addNotification(nr2); + + // Cancel specific notifications via listener. + String[] keys = {nr1.getSbn().getKey(), nr2.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + + // Notifications should not be active anymore. + StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); + assertThat(notifications).isEmpty(); + assertEquals(0, mService.getNotificationRecordCount()); + // Ensure cancel event is not logged due to flag being disabled. + verify(mAppOpsManager, never()).noteOpNoThrow( + eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), + any(), any()); + } + + @Test + public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll() + throws RemoteException { + mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags + .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); + + // Create recent notification. + final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, + System.currentTimeMillis()); + mService.addNotification(nr1); + + // Create old notification. + final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); + mService.addNotification(nr2); + + // Cancel all notifications via listener. + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + + // Notifications should not be active anymore. + StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); + assertThat(notifications).isEmpty(); + assertEquals(0, mService.getNotificationRecordCount()); + // Ensure cancel event is logged. + verify(mAppOpsManager).noteOpNoThrow( + AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null, null); + } + + @Test + public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException { + mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags + .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); + + // Create old notifications. + final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0); + mService.addNotification(nr1); + + final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); + mService.addNotification(nr2); + + // Cancel all notifications via listener. + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + + // Notifications should not be active anymore. + StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); + assertThat(notifications).isEmpty(); + assertEquals(0, mService.getNotificationRecordCount()); + // Ensure cancel event is not logged. + verify(mAppOpsManager, never()).noteOpNoThrow( + eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), + any(), any()); + } + + @Test + public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled() + throws RemoteException { + mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags + .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED); + + // Create recent notification. + final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, + System.currentTimeMillis()); + mService.addNotification(nr1); + + // Create old notification. + final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0); + mService.addNotification(nr2); + + // Cancel all notifications via listener. + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + + // Notifications should not be active anymore. + StatusBarNotification[] notifications = mBinderService.getActiveNotifications(PKG); + assertThat(notifications).isEmpty(); + assertEquals(0, mService.getNotificationRecordCount()); + // Ensure cancel event is not logged due to flag being disabled. + verify(mAppOpsManager, never()).noteOpNoThrow( + eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(), + any(), any()); + } + private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index fe21103096ca..5ddac034d116 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -67,7 +67,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -2370,7 +2369,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // create notification channel that can bypass dnd @@ -2380,18 +2379,18 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // delete channels mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2407,7 +2406,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // Recreate a channel & now the app has dnd access granted and can set the bypass dnd field @@ -2417,7 +2416,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2433,7 +2432,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // create notification channel that can bypass dnd, using local app level settings @@ -2443,18 +2442,18 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // delete channels mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2481,8 +2480,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), - anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2504,7 +2502,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2526,7 +2524,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2542,7 +2540,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // update channel so it CAN bypass dnd: @@ -2550,7 +2548,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel.setBypassDnd(true); mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true); assertTrue(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // update channel so it can't bypass dnd: @@ -2558,7 +2556,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel.setBypassDnd(false); mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2571,7 +2569,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper.syncChannelsBypassingDnd(); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2581,7 +2579,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index ef6fcedf7339..1aea56cd8f9f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -45,6 +45,12 @@ import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.provider.Settings.Global.ZEN_MODE_OFF; import static android.service.notification.Condition.STATE_FALSE; import static android.service.notification.Condition.STATE_TRUE; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED; @@ -53,9 +59,6 @@ import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED; import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG; import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW; import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW; -import static com.android.server.notification.ZenModeHelper.FROM_APP; -import static com.android.server.notification.ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI; -import static com.android.server.notification.ZenModeHelper.FROM_USER; import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE; import static com.google.common.truth.Truth.assertThat; @@ -77,6 +80,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.notNull; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -262,10 +266,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { .thenReturn(CUSTOM_PKG_UID); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( new String[] {pkg}); - ApplicationInfo mockAppInfo = mock(ApplicationInfo.class); - when(mockAppInfo.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL); + + ApplicationInfo appInfoSpy = spy(new ApplicationInfo()); + appInfoSpy.icon = ICON_RES_ID; + when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL); when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt())) - .thenReturn(mockAppInfo); + .thenReturn(appInfoSpy); mZenModeHelper.mPm = mPackageManager; mZenModeEventLogger.reset(); @@ -305,8 +311,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL); serializer.endDocument(); serializer.flush(); - mZenModeHelper.setConfig(new ZenModeConfig(), null, "writing xml", Process.SYSTEM_UID, - true); + mZenModeHelper.setConfig(new ZenModeConfig(), null, UPDATE_ORIGIN_INIT, "writing xml", + Process.SYSTEM_UID); return baos; } @@ -321,7 +327,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { serializer.flush(); ZenModeConfig newConfig = new ZenModeConfig(); newConfig.user = userId; - mZenModeHelper.setConfig(newConfig, null, "writing xml", Process.SYSTEM_UID, true); + mZenModeHelper.setConfig(newConfig, null, UPDATE_ORIGIN_INIT, "writing xml", + Process.SYSTEM_UID); return baos; } @@ -838,7 +845,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mConfig = null; // will evaluate config to zen mode off for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenModeLocked("test", true); + mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -865,7 +872,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenModeLocked("test", true); + mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -892,7 +899,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenModeLocked("test", true); + mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -905,8 +912,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // Turn manual zen mode on - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, - "test", CUSTOM_PKG_UID, false); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, + null, "test", CUSTOM_PKG_UID); // audio manager shouldn't do anything until the handler processes its messages verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal(); @@ -1185,7 +1192,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { List<StatsEvent> events = new LinkedList<>(); mZenModeHelper.pullRules(events); - mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, "test", CUSTOM_PKG_UID, false); + mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); assertTrue(-1 == mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1)); } @@ -1240,12 +1248,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { config10.user = 10; config10.allowAlarms = true; config10.allowMedia = true; - mZenModeHelper.setConfig(config10, null, "writeXml", Process.SYSTEM_UID, true); + mZenModeHelper.setConfig(config10, null, UPDATE_ORIGIN_INIT, "writeXml", + Process.SYSTEM_UID); ZenModeConfig config11 = mZenModeHelper.mConfig.copy(); config11.user = 11; config11.allowAlarms = false; config11.allowMedia = false; - mZenModeHelper.setConfig(config11, null, "writeXml", Process.SYSTEM_UID, true); + mZenModeHelper.setConfig(config11, null, UPDATE_ORIGIN_INIT, "writeXml", + Process.SYSTEM_UID); // Backup user 10 and reset values. ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10); @@ -1849,8 +1859,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); // We need the package name to be something that's not "android" so there aren't any // existing rules under that package. - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); assertNotNull(id); } try { @@ -1860,8 +1870,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1881,8 +1891,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(si), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); assertNotNull(id); } try { @@ -1892,8 +1902,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1913,8 +1923,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(si), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); assertNotNull(id); } try { @@ -1924,8 +1934,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1940,8 +1950,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); @@ -1961,8 +1971,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ComponentName("android", "ScheduleConditionProvider"), ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); @@ -1985,11 +1995,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(), new Condition(zenRule.getConditionId(), "", STATE_TRUE), - CUSTOM_PKG_UID, false); + UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertEquals(STATE_TRUE, ruleInConfig.condition.state); @@ -2004,8 +2015,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW", null, @@ -2014,7 +2025,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, FROM_APP); + mZenModeHelper.updateAutomaticZenRule(id, zenRule2, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertEquals("NEW", ruleInConfig.name); @@ -2029,15 +2040,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertTrue(ruleInConfig != null); assertEquals(zenRule.getName(), ruleInConfig.name); - mZenModeHelper.removeAutomaticZenRule(id, "test", CUSTOM_PKG_UID, false); + mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID); assertNull(mZenModeHelper.mConfig.automaticRules.get(id)); } @@ -2049,16 +2060,16 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertTrue(ruleInConfig != null); assertEquals(zenRule.getName(), ruleInConfig.name); - mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), "test", - CUSTOM_PKG_UID, false); + mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); assertNull(mZenModeHelper.mConfig.automaticRules.get(id)); } @@ -2073,17 +2084,18 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); Condition condition = new Condition(sharedUri, "", STATE_TRUE); - mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true); + mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) { if (rule.id.equals(id)) { @@ -2097,7 +2109,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { } condition = new Condition(sharedUri, "", STATE_FALSE); - mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true); + mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) { if (rule.id.equals(id)) { @@ -2133,7 +2146,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(zde) .build(), - "reasons", 0, FROM_APP); + UPDATE_ORIGIN_APP, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo( @@ -2167,7 +2180,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(zde) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); @@ -2195,7 +2208,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(zde) .build(), - "reasons", 0, FROM_USER); + UPDATE_ORIGIN_USER, + "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); @@ -2212,7 +2226,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // Good @@ -2223,7 +2237,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(updateFromApp) .build(), - "reasons", 0, FROM_APP); + UPDATE_ORIGIN_APP, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo( @@ -2244,7 +2258,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // Good @@ -2254,7 +2268,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new AutomaticZenRule.Builder("Rule", CONDITION_ID) .setDeviceEffects(updateFromSystem) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem); @@ -2271,7 +2285,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // Good @@ -2281,7 +2295,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new AutomaticZenRule.Builder("Rule", CONDITION_ID) .setDeviceEffects(updateFromUser) .build(), - "reasons", 0, FROM_USER); + UPDATE_ORIGIN_USER, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser); @@ -2293,16 +2307,16 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // note that caller=null because that's how it comes in from NMS.setZenMode - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // confirm that setting zen mode via setManualZenMode changed the zen mode correctly assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode); assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation); // and also that it works to turn it back off again - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", null, Process.SYSTEM_UID); assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode); } @@ -2312,15 +2326,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // note that caller=null because that's how it comes in from NMS.setZenMode - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // confirm that setting zen mode via setManualZenMode changed the zen mode correctly assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode); // and also that it works to turn it back off again - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + null, Process.SYSTEM_UID); assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode); } @@ -2332,13 +2346,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Turn zen mode on (to important_interruptions) // Need to additionally call the looper in order to finish the post-apply-config process - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // Now turn zen mode off, but via a different package UID -- this should get registered as // "not an action by the user" because some other app is changing zen mode - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID, - false); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", null, + CUSTOM_PKG_UID); // In total, this should be 2 loggable changes assertEquals(2, mZenModeEventLogger.numLoggedChanges()); @@ -2397,17 +2411,18 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, - "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + Process.SYSTEM_UID); // Event 2: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + Process.SYSTEM_UID); // Add a new system rule AutomaticZenRule systemRule = new AutomaticZenRule("systemRule", @@ -2417,15 +2432,16 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule, - "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Event 3: turn on the system rule mZenModeHelper.setAutomaticZenRuleState(systemId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Event 4: "User" deletes the rule - mZenModeHelper.removeAutomaticZenRule(systemId, "", Process.SYSTEM_UID, true); + mZenModeHelper.removeAutomaticZenRule(systemId, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + Process.SYSTEM_UID); // In total, this represents 4 events assertEquals(4, mZenModeEventLogger.numLoggedChanges()); @@ -2486,26 +2502,26 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // First just turn zen mode on - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // Now change the policy slightly; want to confirm that this'll be reflected in the logs ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); newConfig.allowAlarms = true; newConfig.allowRepeatCallers = false; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode // is off. - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + null, Process.SYSTEM_UID); // Change the policy again newConfig.allowMessages = false; newConfig.allowRepeatCallers = true; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Total events: we only expect ones for turning on, changing policy, and turning off assertEquals(3, mZenModeEventLogger.numLoggedChanges()); @@ -2548,8 +2564,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Rule 2, same as rule 1 AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", @@ -2558,8 +2574,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Rule 3, has stricter settings than the default settings ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy(); @@ -2572,28 +2588,28 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), ruleConfig.toZenPolicy(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // First: turn on rule 1 mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Second: turn on rule 2 mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Third: turn on rule 3 mZenModeHelper.setAutomaticZenRuleState(id3, new Condition(zenRule3.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Fourth: Turn *off* rule 2 mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_FALSE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // This should result in a total of four events assertEquals(4, mZenModeEventLogger.numLoggedChanges()); @@ -2649,7 +2665,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off // given that we don't have any zen rules active. mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.evaluateZenModeLocked("test", true); + mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true); // Check that the change actually took: zen mode should be off now assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode); @@ -2672,8 +2688,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Rule 2, same as rule 1 but owned by the system AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", @@ -2682,37 +2698,37 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Turn on rule 1; call looks like it's from the system. Because setting a condition is // typically an automatic (non-user-initiated) action, expect the calling UID to be // re-evaluated to the one associat.d with CUSTOM_PKG_NAME. mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Second: turn on rule 2. This is a system-owned rule and the UID should not be modified // (nor even looked up; the mock PackageManager won't handle "android" as input). mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Disable rule 1. Because this looks like a user action, the UID should not be modified // from the system-provided one. zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + Process.SYSTEM_UID); // Add a manual rule. Any manual rule changes should not get calling uids reassigned. - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - CUSTOM_PKG_UID, false); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, + "", null, CUSTOM_PKG_UID); // Change rule 2's condition, but from some other UID. Since it doesn't look like it's from // the system, we keep the UID info. mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_FALSE), - 12345, false); + UPDATE_ORIGIN_APP, 12345); // That was 5 events total assertEquals(5, mZenModeEventLogger.numLoggedChanges()); @@ -2767,26 +2783,26 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Turn on zen mode with a manual rule with an enabler set. This should *not* count // as a user action, and *should* get its UID reassigned. mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - CUSTOM_PKG_NAME, "", Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", CUSTOM_PKG_NAME, Process.SYSTEM_UID); // Now change apps bypassing to true ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); newConfig.areChannelsBypassingDnd = true; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // and then back to false, all without changing anything else newConfig.areChannelsBypassingDnd = false; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Turn off manual mode, call from a package: don't reset UID even though enabler is set - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, - CUSTOM_PKG_NAME, "", 12345, false); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", + CUSTOM_PKG_NAME, 12345); // And likewise when turning it back on again - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - CUSTOM_PKG_NAME, "", 12345, false); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, + "", CUSTOM_PKG_NAME, 12345); // These are 5 events in total. assertEquals(5, mZenModeEventLogger.numLoggedChanges()); @@ -2831,15 +2847,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // First just turn zen mode on - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // Now change only the channels part of the policy; want to confirm that this'll be // reflected in the logs ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); newConfig.allowPriorityChannels = false; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Total events: one for turning on, one for changing policy assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2); @@ -2881,13 +2897,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable the rule mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // inspect the consolidated policy. Based on setupZenConfig() values. assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); @@ -2924,13 +2940,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), customPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable the rule; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // since this is the only active rule, the consolidated policy should match the custom // policy for every field specified, and take default values for unspecified things @@ -2960,13 +2976,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable rule 1 mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // custom policy for rule 2 ZenPolicy customPolicy = new ZenPolicy.Builder() @@ -2985,12 +3001,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { customPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, - "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable rule 2; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // now both rules should be on, and the consolidated policy should reflect the most // restrictive option of each of the two @@ -3022,13 +3038,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), customPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable the rule; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // confirm that channels make it through assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels()); @@ -3045,12 +3061,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { strictPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, - "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable rule 2; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // rule 2 should override rule 1 assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels()); @@ -3121,7 +3137,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, true, FROM_APP); + mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true); assertEquals(NAME, rule.name); assertEquals(OWNER, rule.component); @@ -3149,7 +3165,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -3165,8 +3181,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.addCallback(callback); zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]); @@ -3184,7 +3200,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, false); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -3200,8 +3216,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.addCallback(callback); zenRule.setEnabled(true); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]); @@ -3220,7 +3236,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -3237,7 +3253,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]); @@ -3256,7 +3272,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[2]; @@ -3274,10 +3290,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + null, "", Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]); @@ -3296,7 +3312,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[2]; @@ -3314,11 +3330,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_FALSE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]); @@ -3336,21 +3352,21 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Event 2: Snooze rule by turning off DND - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", null, Process.SYSTEM_UID); // Event 3: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", Process.SYSTEM_UID); assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing); } @@ -3359,18 +3375,20 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testDeviceEffects_applied() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT)); ZenDeviceEffects effects = new ZenDeviceEffects.Builder() .setShouldSuppressAmbientDisplay(true) .setShouldDimWallpaper(true) .build(); String ruleId = addRuleWithEffects(effects); - verify(mDeviceEffectsApplier, never()).apply(any()); + verifyNoMoreInteractions(mDeviceEffectsApplier); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier).apply(eq(effects)); + verify(mDeviceEffectsApplier).apply(eq(effects), eq(UPDATE_ORIGIN_APP)); } @Test @@ -3380,31 +3398,66 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); String ruleId = addRuleWithEffects(zde); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier).apply(eq(zde)); + verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP)); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS)); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_APP)); } @Test + public void testDeviceEffects_changeToConsolidatedEffects_applied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT)); + + String ruleId = addRuleWithEffects( + new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build()); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); + mTestableLooper.processAllMessages(); + verify(mDeviceEffectsApplier).apply( + eq(new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(true) + .build()), + eq(UPDATE_ORIGIN_APP)); + + // Now create and activate a second rule that adds more effects. + String secondRuleId = addRuleWithEffects( + new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build()); + mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); + mTestableLooper.processAllMessages(); + + verify(mDeviceEffectsApplier).apply( + eq(new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(true) + .setShouldDimWallpaper(true) + .build()), + eq(UPDATE_ORIGIN_APP)); + } + @Test public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT)); ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); String ruleId = addRuleWithEffects(zde); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier).apply(eq(zde)); + verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP)); // Now create and activate a second rule that doesn't add any more effects. String secondRuleId = addRuleWithEffects(zde); - mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, CUSTOM_PKG_UID, - false); + mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); verifyNoMoreInteractions(mDeviceEffectsApplier); @@ -3416,21 +3469,50 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); String ruleId = addRuleWithEffects(zde); - verify(mDeviceEffectsApplier, never()).apply(any()); + verify(mDeviceEffectsApplier, never()).apply(any(), anyInt()); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier, never()).apply(any()); + verify(mDeviceEffectsApplier, never()).apply(any(), anyInt()); mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); - verify(mDeviceEffectsApplier).apply(eq(zde)); + verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_INIT)); + } + + @Test + public void testDeviceEffects_onUserSwitch_appliedImmediately() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT)); + + // Initialize default configurations (default rules) for both users. + mZenModeHelper.onUserSwitched(1); + mZenModeHelper.onUserSwitched(2); + + // Current user is now 2. Tweak a rule for user 1 so it's active and has effects. + ZenRule user1Rule = mZenModeHelper.mConfigs.get(1).automaticRules.valueAt(0); + user1Rule.enabled = true; + user1Rule.condition = new Condition(user1Rule.conditionId, "on", STATE_TRUE); + user1Rule.zenDeviceEffects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldUseNightMode(true) + .build(); + verifyNoMoreInteractions(mDeviceEffectsApplier); + + mZenModeHelper.onUserSwitched(1); + mTestableLooper.processAllMessages(); + + verify(mDeviceEffectsApplier).apply(eq(user1Rule.zenDeviceEffects), + eq(UPDATE_ORIGIN_INIT_USER)); } private String addRuleWithEffects(ZenDeviceEffects effects) { AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID) .setDeviceEffects(effects) .build(); - return mZenModeHelper.addAutomaticZenRule("pkg", rule, "", CUSTOM_PKG_UID, FROM_APP); + return mZenModeHelper.addAutomaticZenRule("pkg", rule, UPDATE_ORIGIN_APP, "", + CUSTOM_PKG_UID); } @Test @@ -3455,7 +3537,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS); - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0); assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, @@ -3505,7 +3587,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse(); - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0); assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue(); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, @@ -3674,6 +3756,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.zenPolicy = policy; rule.pkg = ownerPkg; rule.name = CUSTOM_APP_LABEL; + rule.iconResName = ICON_RES_NAME; + rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description, + CUSTOM_APP_LABEL); + rule.type = AutomaticZenRule.TYPE_OTHER; rule.enabled = true; return rule; } 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..159c760992c6 --- /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 setShouldReceiveSandboxedTrainingData_currentAndPreinstalledAssistant_setsOp() { + // Set application info so current app is the current and preinstalled assistant. + mApplicationInfo.uid = Process.myUid(); + mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + + mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData( + /* 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 setShouldReceiveSandboxedTrainingData_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.setShouldReceiveSandboxedTrainingData( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } + + @Test + public void setShouldReceiveSandboxedTrainingData_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.setShouldReceiveSandboxedTrainingData( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } + + @Test + public void setShouldReceiveSandboxedTrainingData_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.setShouldReceiveSandboxedTrainingData( + /* allowed= */ true)); + + verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); + } +} diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index c3074bb0fee8..ef197918deff 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -99,11 +99,6 @@ android:theme="@style/WhiteBackgroundTheme" android:exported="true"/> - <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity" - android:exported="true" - android:showWhenLocked="true" - android:turnScreenOn="true" /> - <activity android:name="android.app.Activity" android:exported="true" android:showWhenLocked="true" diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java index 8d236eda5dc5..0382ca0d9fec 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java @@ -61,6 +61,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { META_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC); META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING); } + private static final int ANY_DISPLAY_ID = 123; @Before public void setUp() { @@ -96,8 +97,9 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { */ @Test public void testCtrlSpace() { - sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, 0); - mPhoneWindowManager.assertSwitchKeyboardLayout(1); + sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, /* duration= */ 0, + ANY_DISPLAY_ID); + mPhoneWindowManager.assertSwitchKeyboardLayout(/* direction= */ 1, ANY_DISPLAY_ID); } /** @@ -105,8 +107,9 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { */ @Test public void testCtrlShiftSpace() { - sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE}, 0); - mPhoneWindowManager.assertSwitchKeyboardLayout(-1); + sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE}, + /* duration= */ 0, ANY_DISPLAY_ID); + mPhoneWindowManager.assertSwitchKeyboardLayout(/* direction= */ -1, ANY_DISPLAY_ID); } /** diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java index 9cdec2588501..157d1627d993 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -114,7 +114,7 @@ class ShortcutKeyTestBase { } } - void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress) { + void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress, int displayId) { final long downTime = mPhoneWindowManager.getCurrentTime(); final int count = keyCodes.length; int metaState = 0; @@ -124,7 +124,7 @@ class ShortcutKeyTestBase { final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); - event.setDisplayId(DEFAULT_DISPLAY); + event.setDisplayId(displayId); interceptKey(event); // The order is important here, metaState could be updated and applied to the next key. metaState |= MODIFIER.getOrDefault(keyCode, 0); @@ -142,7 +142,7 @@ class ShortcutKeyTestBase { KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD); - nextDownEvent.setDisplayId(DEFAULT_DISPLAY); + nextDownEvent.setDisplayId(displayId); interceptKey(nextDownEvent); } } @@ -153,18 +153,23 @@ class ShortcutKeyTestBase { final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, InputDevice.SOURCE_KEYBOARD); - upEvent.setDisplayId(DEFAULT_DISPLAY); + upEvent.setDisplayId(displayId); interceptKey(upEvent); metaState &= ~MODIFIER.getOrDefault(keyCode, 0); } } void sendKeyCombination(int[] keyCodes, long durationMillis) { - sendKeyCombination(keyCodes, durationMillis, false /* longPress */); + sendKeyCombination(keyCodes, durationMillis, false /* longPress */, DEFAULT_DISPLAY); + } + + void sendKeyCombination(int[] keyCodes, long durationMillis, int displayId) { + sendKeyCombination(keyCodes, durationMillis, false /* longPress */, displayId); } void sendLongPressKeyCombination(int[] keyCodes) { - sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */); + sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */, + DEFAULT_DISPLAY); } void sendKey(int keyCode) { @@ -172,7 +177,7 @@ class ShortcutKeyTestBase { } void sendKey(int keyCode, boolean longPress) { - sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress); + sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY); } private void interceptKey(KeyEvent keyEvent) { diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index d057226836a3..0678210e1d2f 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -166,12 +166,34 @@ class TestPhoneWindowManager { @Mock private PhoneWindowManager.ButtonOverridePermissionChecker mButtonOverridePermissionChecker; + @Mock private IBinder mInputToken; + @Mock private IBinder mImeTargetWindowToken; + private StaticMockitoSession mMockitoSession; private OffsettableClock mClock = new OffsettableClock(); private TestLooper mTestLooper = new TestLooper(() -> mClock.now()); private HandlerThread mHandlerThread; private Handler mHandler; + private boolean mIsTalkBackEnabled; + + class TestTalkbackShortcutController extends TalkbackShortcutController { + TestTalkbackShortcutController(Context context) { + super(context); + } + + @Override + boolean toggleTalkback(int currentUserId) { + mIsTalkBackEnabled = !mIsTalkBackEnabled; + return mIsTalkBackEnabled; + } + + @Override + boolean isTalkBackShortcutGestureEnabled() { + return true; + } + } + private class TestInjector extends PhoneWindowManager.Injector { TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) { super(context, funcs, mTestLooper.getLooper()); @@ -197,6 +219,10 @@ class TestPhoneWindowManager { PhoneWindowManager.ButtonOverridePermissionChecker getButtonOverridePermissionChecker() { return mButtonOverridePermissionChecker; } + + TalkbackShortcutController getTalkbackShortcutController() { + return new TestTalkbackShortcutController(mContext); + } } TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) { @@ -304,6 +330,9 @@ class TestPhoneWindowManager { doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt(), anyInt()); doNothing().when(mPhoneWindowManager).lockNow(any()); + doReturn(mImeTargetWindowToken) + .when(mWindowManagerInternal).getTargetWindowTokenFromInputToken(mInputToken); + mPhoneWindowManager.init(new TestInjector(mContext, mWindowManagerFuncsImpl)); mPhoneWindowManager.systemReady(); mPhoneWindowManager.systemBooted(); @@ -342,12 +371,12 @@ class TestPhoneWindowManager { } long interceptKeyBeforeDispatching(KeyEvent event) { - return mPhoneWindowManager.interceptKeyBeforeDispatching(null /*focusedToken*/, - event, FLAG_INTERACTIVE); + return mPhoneWindowManager.interceptKeyBeforeDispatching(mInputToken, event, + FLAG_INTERACTIVE); } void dispatchUnhandledKey(KeyEvent event) { - mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE); + mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE); } long getCurrentTime() { @@ -623,14 +652,16 @@ class TestPhoneWindowManager { verify(mStatusBarManagerInternal).startAssist(any()); } - void assertSwitchKeyboardLayout(int direction) { + void assertSwitchKeyboardLayout(int direction, int displayId) { mTestLooper.dispatchAll(); if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) { - verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction)); + verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction), + eq(displayId), eq(mImeTargetWindowToken)); verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt()); } else { verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction)); - verify(mInputMethodManagerInternal, never()).switchKeyboardLayout(anyInt()); + verify(mInputMethodManagerInternal, never()) + .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 65e77dcd4ca9..d4ba3b25178d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -124,7 +124,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { private void verifyOnActivityLaunched(ActivityRecord activity) { final ArgumentCaptor<Long> idCaptor = ArgumentCaptor.forClass(Long.class); verifyAsync(mLaunchObserver).onActivityLaunched(idCaptor.capture(), - eq(activity.mActivityComponent), anyInt()); + eq(activity.mActivityComponent), anyInt(), anyInt()); final long id = idCaptor.getValue(); setExpectedStartedId(id, activity); mLastLaunchedIds.put(activity.mActivityComponent, id); @@ -132,7 +132,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { private void verifyOnActivityLaunchFinished(ActivityRecord activity) { verifyAsync(mLaunchObserver).onActivityLaunchFinished(eq(mExpectedStartedId), - eq(activity.mActivityComponent), anyLong()); + eq(activity.mActivityComponent), anyLong(), anyInt()); } private void setExpectedStartedId(long id, Object reason) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 7aa46a62b0f1..85c6f9e9b2fe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -123,6 +123,7 @@ import android.app.ICompatCameraControlCallback; import android.app.PictureInPictureParams; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.WindowStateResizeItem; @@ -169,6 +170,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -260,8 +262,18 @@ public class ActivityRecordTests extends WindowTestsBase { final MutableBoolean pauseFound = new MutableBoolean(false); doAnswer((InvocationOnMock invocationOnMock) -> { final ClientTransaction transaction = invocationOnMock.getArgument(0); - if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) { - pauseFound.value = true; + final List<ClientTransactionItem> items = transaction.getTransactionItems(); + if (items != null) { + for (ClientTransactionItem item : items) { + if (item instanceof PauseActivityItem) { + pauseFound.value = true; + break; + } + } + } else { + if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) { + pauseFound.value = true; + } } return null; }).when(mClientLifecycleManager).scheduleTransaction(any()); @@ -279,6 +291,8 @@ public class ActivityRecordTests extends WindowTestsBase { // If the activity is not focusable, it should move to paused. activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */); + mClientLifecycleManager.dispatchPendingTransactions(); + assertTrue(activity.isState(PAUSING)); assertTrue(pauseFound.value); @@ -1239,7 +1253,7 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test - public void testFinishActivityIfPossible_sendResultImmediately() { + public void testFinishActivityIfPossible_sendResultImmediately() throws RemoteException { // Create activity representing the source of the activity result. final ComponentName sourceComponent = ComponentName.createRelative( DEFAULT_COMPONENT_PACKAGE_NAME, ".SourceActivity"); @@ -1270,12 +1284,8 @@ public class ActivityRecordTests extends WindowTestsBase { targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */); - try { - verify(mClientLifecycleManager, atLeastOnce()).scheduleTransaction( - any(ClientTransaction.class)); - } catch (RemoteException ignored) { - } - + verify(mClientLifecycleManager, atLeastOnce()).scheduleTransactionItem( + any(), any(ClientTransactionItem.class)); assertNull(targetActivity.results); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java index 04aa9815e698..7fdc5fc2cff6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java @@ -17,13 +17,15 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; @@ -31,12 +33,15 @@ import android.app.IApplicationThread; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; +import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; @@ -47,30 +52,47 @@ import org.mockito.MockitoAnnotations; * Build/Install/Run: * atest WmTests:ClientLifecycleManagerTests */ +// Suppress GuardedBy warning on unit tests +@SuppressWarnings("GuardedBy") @SmallTest @Presubmit public class ClientLifecycleManagerTests { + @Rule(order = 0) + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Rule(order = 1) + public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule(); + + @Mock + private IBinder mClientBinder; @Mock private IApplicationThread mClient; @Mock private IApplicationThread.Stub mNonBinderClient; @Mock + private ClientTransaction mTransaction; + @Mock private ClientTransactionItem mTransactionItem; @Mock private ActivityLifecycleItem mLifecycleItem; @Captor private ArgumentCaptor<ClientTransaction> mTransactionCaptor; + private WindowManagerService mWms; private ClientLifecycleManager mLifecycleManager; @Before public void setup() { MockitoAnnotations.initMocks(this); + mWms = mSystemServices.getWindowManagerService(); mLifecycleManager = spy(new ClientLifecycleManager()); + mLifecycleManager.setWindowManager(mWms); doReturn(true).when(mLifecycleItem).isActivityLifecycleItem(); + doReturn(mClientBinder).when(mClient).asBinder(); + doReturn(mNonBinderClient).when(mNonBinderClient).asBinder(); } @Test @@ -92,9 +114,11 @@ public class ClientLifecycleManagerTests { } @Test - public void testScheduleTransactionItem() throws RemoteException { - doNothing().when(mLifecycleManager).scheduleTransaction(any()); - mLifecycleManager.scheduleTransactionItem(mClient, mTransactionItem); + public void testScheduleTransactionItem_notBundle() throws RemoteException { + mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG); + + // Use non binder client to get non-recycled ClientTransaction. + mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mTransactionItem); verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture()); ClientTransaction transaction = mTransactionCaptor.getValue(); @@ -104,7 +128,7 @@ public class ClientLifecycleManagerTests { assertNull(transaction.getTransactionItems()); clearInvocations(mLifecycleManager); - mLifecycleManager.scheduleTransactionItem(mClient, mLifecycleItem); + mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem); verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture()); transaction = mTransactionCaptor.getValue(); @@ -113,9 +137,54 @@ public class ClientLifecycleManagerTests { } @Test - public void testScheduleTransactionAndLifecycleItems() throws RemoteException { - doNothing().when(mLifecycleManager).scheduleTransaction(any()); - mLifecycleManager.scheduleTransactionAndLifecycleItems(mClient, mTransactionItem, + public void testScheduleTransactionItem() throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG); + spyOn(mWms.mWindowPlacerLocked); + doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled(); + + // Use non binder client to get non-recycled ClientTransaction. + mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mTransactionItem); + + // When there is traversal scheduled, add transaction items to pending. + assertEquals(1, mLifecycleManager.mPendingTransactions.size()); + ClientTransaction transaction = + mLifecycleManager.mPendingTransactions.get(mNonBinderClient); + assertEquals(1, transaction.getTransactionItems().size()); + assertEquals(mTransactionItem, transaction.getTransactionItems().get(0)); + assertNull(transaction.getCallbacks()); + assertNull(transaction.getLifecycleStateRequest()); + verify(mLifecycleManager, never()).scheduleTransaction(any()); + + // Add new transaction item to the existing pending. + clearInvocations(mLifecycleManager); + mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem); + + assertEquals(1, mLifecycleManager.mPendingTransactions.size()); + transaction = mLifecycleManager.mPendingTransactions.get(mNonBinderClient); + assertEquals(2, transaction.getTransactionItems().size()); + assertEquals(mTransactionItem, transaction.getTransactionItems().get(0)); + assertEquals(mLifecycleItem, transaction.getTransactionItems().get(1)); + assertNull(transaction.getCallbacks()); + assertNull(transaction.getLifecycleStateRequest()); + verify(mLifecycleManager, never()).scheduleTransaction(any()); + } + + @Test + public void testScheduleTransactionItemUnlocked() throws RemoteException { + // Use non binder client to get non-recycled ClientTransaction. + mLifecycleManager.scheduleTransactionItemUnlocked(mNonBinderClient, mTransactionItem); + + // Dispatch immediately. + assertTrue(mLifecycleManager.mPendingTransactions.isEmpty()); + verify(mLifecycleManager).scheduleTransaction(any()); + } + + @Test + public void testScheduleTransactionAndLifecycleItems_notBundle() throws RemoteException { + mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG); + + // Use non binder client to get non-recycled ClientTransaction. + mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem, mLifecycleItem); verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture()); @@ -124,4 +193,36 @@ public class ClientLifecycleManagerTests { assertEquals(mTransactionItem, transaction.getCallbacks().get(0)); assertEquals(mLifecycleItem, transaction.getLifecycleStateRequest()); } + + @Test + public void testScheduleTransactionAndLifecycleItems() throws RemoteException { + mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG); + spyOn(mWms.mWindowPlacerLocked); + doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled(); + + // Use non binder client to get non-recycled ClientTransaction. + mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem, + mLifecycleItem); + + assertEquals(1, mLifecycleManager.mPendingTransactions.size()); + final ClientTransaction transaction = + mLifecycleManager.mPendingTransactions.get(mNonBinderClient); + assertEquals(2, transaction.getTransactionItems().size()); + assertEquals(mTransactionItem, transaction.getTransactionItems().get(0)); + assertEquals(mLifecycleItem, transaction.getTransactionItems().get(1)); + assertNull(transaction.getCallbacks()); + assertNull(transaction.getLifecycleStateRequest()); + verify(mLifecycleManager, never()).scheduleTransaction(any()); + } + + @Test + public void testDispatchPendingTransactions() throws RemoteException { + mLifecycleManager.mPendingTransactions.put(mClientBinder, mTransaction); + + mLifecycleManager.dispatchPendingTransactions(); + + assertTrue(mLifecycleManager.mPendingTransactions.isEmpty()); + verify(mTransaction).schedule(); + verify(mTransaction).recycle(); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java new file mode 100644 index 000000000000..44b69f18eb04 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.hardware.display.DeviceProductInfo.CONNECTION_TO_SINK_UNKNOWN; +import static android.view.RoundedCorner.POSITION_TOP_LEFT; +import static android.view.RoundedCorners.NO_ROUNDED_CORNERS; + +import static com.android.server.wm.DeferredDisplayUpdater.DEFERRABLE_FIELDS; +import static com.android.server.wm.DeferredDisplayUpdater.DIFF_NOT_WM_DEFERRABLE; +import static com.android.server.wm.DeferredDisplayUpdater.DIFF_WM_DEFERRABLE; +import static com.android.server.wm.DeferredDisplayUpdater.calculateDisplayInfoDiff; +import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.annotation.NonNull; +import android.graphics.Insets; +import android.graphics.Rect; +import android.hardware.display.DeviceProductInfo; +import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; +import android.view.Display; +import android.view.DisplayAddress; +import android.view.DisplayCutout; +import android.view.DisplayInfo; +import android.view.DisplayShape; +import android.view.RoundedCorner; +import android.view.RoundedCorners; +import android.view.SurfaceControl.RefreshRateRange; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +/** + * Build/Install/Run: + * atest WmTests:DeferredDisplayUpdaterDiffTest + */ +@SmallTest +@Presubmit +public class DeferredDisplayUpdaterDiffTest { + + private static final Set<String> IGNORED_FIELDS = new HashSet<>(Arrays.asList( + "name" // human-readable name is ignored in equals() checks + )); + + private static final DisplayInfo EMPTY = new DisplayInfo(); + + @Test + public void testCalculateDisplayInfoDiff_allDifferent_returnsChanges() { + final DisplayInfo first = new DisplayInfo(); + final DisplayInfo second = new DisplayInfo(); + makeAllFieldsDifferent(first, second); + + int diff = calculateDisplayInfoDiff(first, second); + + assertWithMessage("Expected to receive a non-zero difference when " + + "there are changes in all fields of DisplayInfo\n" + + "Make sure that you have updated calculateDisplayInfoDiff function after " + + "changing DisplayInfo fields").that(diff).isGreaterThan(0); + } + + @Test + public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsChanges() { + generateWithSingleDifferentField((first, second, field) -> { + int diff = calculateDisplayInfoDiff(first, second); + + assertWithMessage("Expected to receive a non-zero difference when " + + "there are changes in " + field + "\n" + + "Make sure that you have updated calculateDisplayInfoDiff function after " + + "changing DisplayInfo fields").that(diff).isGreaterThan(0); + }); + } + + @Test + public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsMatchingChange() { + generateWithSingleDifferentField((first, second, field) -> { + boolean hasDeferrableFieldChange = hasDeferrableFieldChange(first, second); + int expectedDiff = + hasDeferrableFieldChange ? DIFF_WM_DEFERRABLE : DIFF_NOT_WM_DEFERRABLE; + + int diff = calculateDisplayInfoDiff(first, second); + + assertWithMessage("Expected to have diff = " + expectedDiff + + ", for field = " + field + "\n" + + "Make sure that you have updated calculateDisplayInfoDiff function after " + + "changing DisplayInfo fields").that( + diff).isEqualTo(expectedDiff); + }); + } + + /** + * Sets each field of the objects to different values using reflection + */ + private static void makeAllFieldsDifferent(@NonNull DisplayInfo first, + @NonNull DisplayInfo second) { + forEachDisplayInfoField(field -> { + try { + setDifferentFieldValues(first, second, field); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private static boolean hasDeferrableFieldChange(@NonNull DisplayInfo first, + @NonNull DisplayInfo second) { + final DisplayInfo firstDeferrableFieldsOnly = new DisplayInfo(); + final DisplayInfo secondDeferrableFieldsOnly = new DisplayInfo(); + + copyDisplayInfoFields(/* out= */ firstDeferrableFieldsOnly, /* base= */ + EMPTY, /* override= */ first, DEFERRABLE_FIELDS); + copyDisplayInfoFields(/* out= */ secondDeferrableFieldsOnly, /* base= */ + EMPTY, /* override= */ second, DEFERRABLE_FIELDS); + + return !firstDeferrableFieldsOnly.equals(secondDeferrableFieldsOnly); + } + + /** + * Creates pairs of DisplayInfos where only one field is different, the callback is called for + * each field + */ + private static void generateWithSingleDifferentField(DisplayInfoConsumer consumer) { + forEachDisplayInfoField(field -> { + final DisplayInfo first = new DisplayInfo(); + final DisplayInfo second = new DisplayInfo(); + + try { + setDifferentFieldValues(first, second, field); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + + consumer.consume(first, second, field); + }); + } + + private static void setDifferentFieldValues(@NonNull DisplayInfo first, + @NonNull DisplayInfo second, + @NonNull Field field) throws IllegalAccessException { + final Class<?> type = field.getType(); + if (type.equals(int.class)) { + field.setInt(first, 1); + field.setInt(second, 2); + } else if (type.equals(double.class)) { + field.setDouble(first, 1.0); + field.setDouble(second, 2.0); + } else if (type.equals(short.class)) { + field.setShort(first, (short) 1); + field.setShort(second, (short) 2); + } else if (type.equals(long.class)) { + field.setLong(first, 1L); + field.setLong(second, 2L); + } else if (type.equals(char.class)) { + field.setChar(first, 'a'); + field.setChar(second, 'b'); + } else if (type.equals(byte.class)) { + field.setByte(first, (byte) 1); + field.setByte(second, (byte) 2); + } else if (type.equals(float.class)) { + field.setFloat(first, 1.0f); + field.setFloat(second, 2.0f); + } else if (type == boolean.class) { + field.setBoolean(first, true); + field.setBoolean(second, false); + } else if (type.equals(String.class)) { + field.set(first, "one"); + field.set(second, "two"); + } else if (type.equals(DisplayAddress.class)) { + field.set(first, DisplayAddress.fromPhysicalDisplayId(0)); + field.set(second, DisplayAddress.fromPhysicalDisplayId(1)); + } else if (type.equals(DeviceProductInfo.class)) { + field.set(first, new DeviceProductInfo("name", "pnp_id", "product_id1", 2023, + CONNECTION_TO_SINK_UNKNOWN)); + field.set(second, new DeviceProductInfo("name", "pnp_id", "product_id2", 2023, + CONNECTION_TO_SINK_UNKNOWN)); + } else if (type.equals(DisplayCutout.class)) { + field.set(first, + new DisplayCutout(Insets.NONE, new Rect(0, 0, 100, 100), null, null, + null)); + field.set(second, + new DisplayCutout(Insets.NONE, new Rect(0, 0, 200, 200), null, null, + null)); + } else if (type.equals(RoundedCorners.class)) { + field.set(first, NO_ROUNDED_CORNERS); + + final RoundedCorners other = new RoundedCorners(NO_ROUNDED_CORNERS); + other.setRoundedCorner(POSITION_TOP_LEFT, + new RoundedCorner(POSITION_TOP_LEFT, 1, 2, 3)); + field.set(second, other); + } else if (type.equals(DisplayShape.class)) { + field.set(first, DisplayShape.createDefaultDisplayShape(100, 200, false)); + field.set(second, DisplayShape.createDefaultDisplayShape(50, 100, false)); + } else if (type.equals(RefreshRateRange.class)) { + field.set(first, new RefreshRateRange(0, 100)); + field.set(second, new RefreshRateRange(20, 80)); + } else if (type.equals(Display.HdrCapabilities.class)) { + field.set(first, new Display.HdrCapabilities(new int[]{0}, 100, 50, 25)); + field.set(second, new Display.HdrCapabilities(new int[]{1}, 100, 50, 25)); + } else if (type.equals(SparseArray.class) + && ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].equals( + RefreshRateRange.class)) { + final SparseArray<RefreshRateRange> array1 = new SparseArray<>(); + array1.set(0, new RefreshRateRange(0, 100)); + final SparseArray<RefreshRateRange> array2 = new SparseArray<>(); + array2.set(0, new RefreshRateRange(20, 80)); + field.set(first, array1); + field.set(second, array2); + } else if (type.isArray() && type.getComponentType().equals(int.class)) { + field.set(first, new int[]{0}); + field.set(second, new int[]{1}); + } else if (type.isArray() && type.getComponentType().equals(Display.Mode.class)) { + field.set(first, new Display.Mode[]{new Display.Mode(100, 200, 300)}); + field.set(second, new Display.Mode[]{new Display.Mode(10, 20, 30)}); + } else { + throw new IllegalArgumentException("Field " + field + + " is not supported by this test, please add implementation of setting " + + "different values for this field"); + } + } + + private interface DisplayInfoConsumer { + void consume(DisplayInfo first, DisplayInfo second, Field field); + } + + /** + * Iterates over every non-static field of DisplayInfo class except IGNORED_FIELDS + */ + private static void forEachDisplayInfoField(Consumer<Field> consumer) { + for (Field field : DisplayInfo.class.getDeclaredFields()) { + field.setAccessible(true); + + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + + if (IGNORED_FIELDS.contains(field.getName())) { + continue; + } + + consumer.accept(field); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java new file mode 100644 index 000000000000..dfa595c23e44 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; +import android.view.DisplayInfo; + +import androidx.test.filters.SmallTest; + +import com.android.server.wm.TransitionController.OnStartCollect; +import com.android.window.flags.Flags; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +/** + * Tests for the {@link DisplayContent} class when FLAG_DEFER_DISPLAY_UPDATES is enabled. + * + * Build/Install/Run: + * atest WmTests:DisplayContentDeferredUpdateTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class DisplayContentDeferredUpdateTests extends WindowTestsBase { + + @Override + protected void onBeforeSystemServicesCreated() { + // Set other flags to their default values + mSetFlagsRule.initAllFlagsToReleaseConfigDefault(); + + mSetFlagsRule.enableFlags(Flags.FLAG_DEFER_DISPLAY_UPDATES); + } + + @Before + public void before() { + mockTransitionsController(/* enabled= */ true); + mockRemoteDisplayChangeController(); + } + + @Test + public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() { + performInitialDisplayUpdate(); + + givenDisplayInfo(/* uniqueId= */ "old"); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + + // Emulate that collection has started + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + verify(onUpdated).run(); + clearInvocations(mDisplayContent.mTransitionController, onUpdated); + + givenDisplayInfo(/* uniqueId= */ "new"); + mDisplayContent.requestDisplayUpdate(onUpdated); + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + verify(onUpdated).run(); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new"); + } + + @Test + public void testUpdate_nonDeferrableUpdateAndTransitionDeferred_nonDeferrableFieldUpdated() { + performInitialDisplayUpdate(); + + // Update only color mode (non-deferrable field) and keep the same unique id + givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + + verify(onUpdated).run(); + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123); + } + + @Test + public void testUpdate_nonDeferrableUpdateTwiceAndTransitionDeferred_fieldHasLatestValue() { + performInitialDisplayUpdate(); + + // Update only color mode (non-deferrable field) and keep the same unique id + givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123); + mDisplayContent.requestDisplayUpdate(mock(Runnable.class)); + + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123); + assertThat(mDisplayContent.getDisplayInfo().uniqueId) + .isEqualTo("initial_unique_id"); + + // Update unique id (deferrable field), keep the same color mode, + // this update should be deferred + givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 123); + mDisplayContent.requestDisplayUpdate(mock(Runnable.class)); + + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123); + assertThat(mDisplayContent.getDisplayInfo().uniqueId) + .isEqualTo("initial_unique_id"); + + // Update color mode again and keep the same unique id, color mode update + // should not be deferred, unique id update is still deferred as transition + // has not started collecting yet + givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 456); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456); + assertThat(mDisplayContent.getDisplayInfo().uniqueId) + .isEqualTo("initial_unique_id"); + + // Mark transition as started collected, so pending changes are applied + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + + // Verify that all fields have the latest values + verify(onUpdated).run(); + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new_unique_id"); + } + + @Test + public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() { + performInitialDisplayUpdate(); + givenDisplayInfo(/* uniqueId= */ "old"); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + verify(onUpdated).run(); + clearInvocations(mDisplayContent.mTransitionController, onUpdated); + + givenDisplayInfo(/* uniqueId= */ "new"); + mDisplayContent.requestDisplayUpdate(onUpdated); + + captureStartTransitionCollection(); // do not continue by not starting the collection + verify(onUpdated, never()).run(); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("old"); + } + + @Test + public void testTwoDisplayUpdates_transitionStarted_displayUpdated() { + performInitialDisplayUpdate(); + givenDisplayInfo(/* uniqueId= */ "old"); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + captureStartTransitionCollection().getValue() + .onCollectStarted(/* deferred= */ true); + verify(onUpdated).run(); + clearInvocations(mDisplayContent.mTransitionController, onUpdated); + + // Perform two display updates while WM is 'busy' + givenDisplayInfo(/* uniqueId= */ "new1"); + Runnable onUpdated1 = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated1); + givenDisplayInfo(/* uniqueId= */ "new2"); + Runnable onUpdated2 = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated2); + + // Continue with the first update + captureStartTransitionCollection().getAllValues().get(0) + .onCollectStarted(/* deferred= */ true); + verify(onUpdated1).run(); + verify(onUpdated2, never()).run(); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new1"); + + // Continue with the second update + captureStartTransitionCollection().getAllValues().get(1) + .onCollectStarted(/* deferred= */ true); + verify(onUpdated2).run(); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new2"); + } + + private void mockTransitionsController(boolean enabled) { + spyOn(mDisplayContent.mTransitionController); + when(mDisplayContent.mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled); + doReturn(true).when(mDisplayContent.mTransitionController).startCollectOrQueue(any(), + any()); + } + + private void mockRemoteDisplayChangeController() { + spyOn(mDisplayContent.mRemoteDisplayChangeController); + doReturn(true).when(mDisplayContent.mRemoteDisplayChangeController) + .performRemoteDisplayChange(anyInt(), anyInt(), any(), any()); + } + + private ArgumentCaptor<OnStartCollect> captureStartTransitionCollection() { + ArgumentCaptor<OnStartCollect> callbackCaptor = + ArgumentCaptor.forClass(OnStartCollect.class); + verify(mDisplayContent.mTransitionController, atLeast(1)).startCollectOrQueue(any(), + callbackCaptor.capture()); + return callbackCaptor; + } + + private void givenDisplayInfo(String uniqueId) { + givenDisplayInfo(uniqueId, /* colorMode= */ 0); + } + + private void givenDisplayInfo(String uniqueId, int colorMode) { + spyOn(mDisplayContent.mDisplay); + doAnswer(invocation -> { + DisplayInfo info = invocation.getArgument(0); + info.uniqueId = uniqueId; + info.colorMode = colorMode; + return null; + }).when(mDisplayContent.mDisplay).getDisplayInfo(any()); + } + + private void performInitialDisplayUpdate() { + givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 0); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + } +} 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/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 50fe0425fe9c..1fb7cd8e6e1c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -553,7 +553,7 @@ public class DragDropControllerTests extends WindowTestsBase { assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(), new InputChannel(), true /* isDragDrop */)); mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient, - flag, surface, 0, 0, 0, 0, 0, data); + flag, surface, 0, 0, 0, 0, 0, 0, 0, data); assertNotNull(mToken); r.run(); @@ -575,7 +575,7 @@ public class DragDropControllerTests extends WindowTestsBase { private void startA11yDrag(int flags, ClipData data, Runnable r) { mToken = mTarget.performDrag(0, 0, mWindow.mClient, - flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, data); + flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, 0, 0, data); assertNotNull(mToken); r.run(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java index c0a90b20c999..e77c14a60179 100644 --- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java @@ -19,10 +19,13 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -62,9 +65,11 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote(); private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT = - new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED = - new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); WindowState createWindow(String name) { WindowState window = createWindow(null, TYPE_APPLICATION, name); @@ -110,6 +115,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { any(SurfaceControl.class), anyInt()); verify(appWindow.getPendingTransaction(), never()).setFrameRate( any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); } @Test @@ -140,6 +147,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), 1); verify(appWindow.getPendingTransaction(), never()).setFrameRate( any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); } @Test @@ -175,8 +184,17 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), 0); verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 1); - verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( + eq(appWindow.getSurfaceControl()), anyFloat(), + eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS)); + if (explicitRefreshRateHints()) { + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + } else { + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); + } } @Test @@ -202,8 +220,17 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 2); - verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( + eq(appWindow.getSurfaceControl()), anyFloat(), + eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS)); + if (explicitRefreshRateHints()) { + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + } else { + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); + } } @Test @@ -229,6 +256,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction(), never()).setFrameRate( any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); } @Test @@ -256,6 +285,14 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS); + if (explicitRefreshRateHints()) { + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + } else { + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); + } } @Test @@ -283,6 +320,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction(), never()).setFrameRate( any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); } @Test @@ -310,5 +349,13 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS); + if (explicitRefreshRateHints()) { + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + } else { + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); + } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index ffa1ed926766..38a66a9d5486 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -19,10 +19,13 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.view.InsetsSource.ID_IME; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; @@ -432,6 +435,56 @@ public class InsetsPolicyTest extends WindowTestsBase { } + @SetupWindows(addWindows = W_INPUT_METHOD) + @Test + public void testConsumeImeInsets() { + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + final InsetsSource imeSource = new InsetsSource(ID_IME, ime()); + imeSource.setVisible(true); + mImeWindow.mHasSurface = true; + + final WindowState win1 = addWindow(TYPE_APPLICATION, "win1"); + final WindowState win2 = addWindow(TYPE_APPLICATION, "win2"); + + win1.mAboveInsetsState.addSource(imeSource); + win1.mHasSurface = true; + win2.mAboveInsetsState.addSource(imeSource); + win2.mHasSurface = true; + + assertTrue(mImeWindow.isVisible()); + assertTrue(win1.isVisible()); + assertTrue(win2.isVisible()); + + // Make sure both windows have visible IME insets. + assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + + win2.mAttrs.privateFlags |= PRIVATE_FLAG_CONSUME_IME_INSETS; + + displayPolicy.beginPostLayoutPolicyLw(); + displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null); + displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null); + displayPolicy.finishPostLayoutPolicyLw(); + + // Make sure win2 doesn't have visible IME insets, but win1 still does. + assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertFalse(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertTrue(win1.getWindowFrames().hasInsetsChanged()); + + win2.mAttrs.privateFlags &= ~PRIVATE_FLAG_CONSUME_IME_INSETS; + win2.getWindowFrames().setInsetsChanged(false); + + displayPolicy.beginPostLayoutPolicyLw(); + displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null); + displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null); + displayPolicy.finishPostLayoutPolicyLw(); + + // Make sure both windows have visible IME insets. + assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertTrue(win1.getWindowFrames().hasInsetsChanged()); + } + private WindowState addNavigationBar() { final Binder owner = new Binder(); final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar"); diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java index 49a888689e60..c9a83b0bc2ee 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -69,15 +70,20 @@ public class RefreshRatePolicyTest extends WindowTestsBase { private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote(); private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST = - new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT = - new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT = - new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED = - new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED = - new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); // Parcel and Unparcel the LayoutParams in the window state to test the path the object // travels from the app's process to system server diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java index 7eab06ac8b95..89cd7266915c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -1240,7 +1240,7 @@ public class RootTaskTests extends WindowTestsBase { final ActivityRecord activity1 = finishTopActivity(rootTask1); assertEquals(DESTROYING, activity1.getState()); verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */, - eq(display.mDisplayId), anyBoolean(), anyBoolean()); + eq(display.mDisplayId), anyBoolean()); } private ActivityRecord finishTopActivity(Task task) { diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 0608cf4abfde..28fecd665662 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -838,7 +838,7 @@ public class RootWindowContainerTests extends WindowTestsBase { .setSystemDecorations(true).build(); doReturn(true).when(mRootWindowContainer) - .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean()); + .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean()); doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(), anyBoolean()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java deleted file mode 100644 index c5dd447b5b0c..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java +++ /dev/null @@ -1,154 +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 com.android.server.wm; - -import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule; -import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.app.Activity; -import android.platform.test.annotations.Presubmit; -import android.server.wm.CtsWindowInfoUtils; -import android.view.SurfaceControl; -import android.view.SurfaceControl.TrustedPresentationThresholds; - -import androidx.annotation.GuardedBy; -import androidx.test.ext.junit.rules.ActivityScenarioRule; - -import com.android.server.wm.utils.CommonUtils; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; - -import java.util.function.Consumer; - -/** - * TODO (b/287076178): Move these tests to - * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public - */ -@Presubmit -public class TrustedPresentationCallbackTest { - private static final String TAG = "TrustedPresentationCallbackTest"; - private static final int STABILITY_REQUIREMENT_MS = 500; - private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L; - - private static final float FRACTION_VISIBLE = 0.1f; - - private final Object mResultsLock = new Object(); - @GuardedBy("mResultsLock") - private boolean mResult; - @GuardedBy("mResultsLock") - private boolean mReceivedResults; - - @Rule - public TestName mName = new TestName(); - - @Rule - public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule( - TestActivity.class); - - private TestActivity mActivity; - - @Before - public void setup() { - mActivityRule.getScenario().onActivity(activity -> mActivity = activity); - } - - @After - public void tearDown() { - CommonUtils.waitUntilActivityRemoved(mActivity); - } - - @Test - public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException { - TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds( - 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds, - Runnable::run, inTrustedPresentationState -> { - synchronized (mResultsLock) { - mResult = inTrustedPresentationState; - mReceivedResults = true; - mResultsLock.notify(); - } - }); - t.apply(); - synchronized (mResultsLock) { - assertResults(); - } - } - - @Test - public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException { - TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds( - 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); - Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> { - synchronized (mResultsLock) { - mResult = inTrustedPresentationState; - mReceivedResults = true; - mResultsLock.notify(); - } - }; - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds, - Runnable::run, trustedPresentationCallback); - t.apply(); - - synchronized (mResultsLock) { - if (!mReceivedResults) { - mResultsLock.wait(WAIT_TIME_MS); - } - assertResults(); - // reset the state - mReceivedResults = false; - } - - mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t, - trustedPresentationCallback); - t.apply(); - - synchronized (mResultsLock) { - if (!mReceivedResults) { - mResultsLock.wait(WAIT_TIME_MS); - } - // Ensure we waited the full time and never received a notify on the result from the - // callback. - assertFalse("Should never have received a callback", mReceivedResults); - // results shouldn't have changed. - assertTrue(mResult); - } - } - - @GuardedBy("mResultsLock") - private void assertResults() throws InterruptedException { - mResultsLock.wait(WAIT_TIME_MS); - - if (!mReceivedResults) { - CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName()); - } - // Make sure we received the results and not just timed out - assertTrue("Timed out waiting for results", mReceivedResults); - assertTrue(mResult); - } - - public static class TestActivity extends Activity { - } -} 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..c902d4597571 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -1569,13 +1569,13 @@ public class VoiceInteractionManagerService extends SystemService { @Override @EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) - public void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed) { - super.setIsReceiveSandboxedTrainingDataAllowed_enforcePermission(); + public void setShouldReceiveSandboxedTrainingData(boolean allowed) { + super.setShouldReceiveSandboxedTrainingData_enforcePermission(); synchronized (this) { if (mImpl == null) { throw new IllegalStateException( - "setIsReceiveSandboxedTrainingDataAllowed without running voice " + "setShouldReceiveSandboxedTrainingData without running voice " + "interaction service"); } @@ -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/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 6a77b983f963..7cb2cc398c46 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9892,7 +9892,6 @@ public class CarrierConfigManager { * * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT - * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED */ public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java index eac4d1682aa9..cc768bc00250 100644 --- a/telephony/java/android/telephony/CarrierRestrictionRules.java +++ b/telephony/java/android/telephony/CarrierRestrictionRules.java @@ -16,12 +16,16 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.service.carrier.CarrierIdentifier; +import android.telephony.TelephonyManager.CarrierRestrictionStatus; + +import com.android.internal.telephony.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -104,7 +108,7 @@ public final class CarrierRestrictionRules implements Parcelable { private int mCarrierRestrictionDefault; @MultiSimPolicy private int mMultiSimPolicy; - @TelephonyManager.CarrierRestrictionStatus + @CarrierRestrictionStatus private int mCarrierRestrictionStatus; private CarrierRestrictionRules() { @@ -293,8 +297,22 @@ public final class CarrierRestrictionRules implements Parcelable { return true; } - /** @hide */ - public int getCarrierRestrictionStatus() { + /** + * Get the carrier restriction status of the device. + * The return value of the API is as follows. + * <ul> + * <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER} + * if the caller and the device locked by the network are same</li> + * <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_RESTRICTED} if the + * caller and the device locked by the network are different</li> + * <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED} if the + * device is not locked</li> + * <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_UNKNOWN} if the device + * locking state is unavailable or radio does not supports the feature</li> + * </ul> + */ + @FlaggedApi(Flags.FLAG_CARRIER_RESTRICTION_STATUS) + public @CarrierRestrictionStatus int getCarrierRestrictionStatus() { return mCarrierRestrictionStatus; } diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java index a004cc3a1642..25063606228c 100644 --- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java +++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.WorkerThread; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.text.Editable; @@ -39,6 +40,9 @@ import java.util.Locale; * </ul> * <p> * The formatting will be restarted once the text is cleared. + * + * @deprecated This is a thin wrapper on a `libphonenumber` `AsYouTypeFormatter`; it is recommended + * to use that instead. */ public class PhoneNumberFormattingTextWatcher implements TextWatcher { @@ -69,6 +73,7 @@ public class PhoneNumberFormattingTextWatcher implements TextWatcher { * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region * where the phone number is being entered. */ + @WorkerThread public PhoneNumberFormattingTextWatcher(String countryCode) { if (countryCode == null) throw new IllegalArgumentException(); mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index e12a815a84f5..326b6f5af613 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -109,10 +109,6 @@ import java.util.stream.Collectors; * Then for SDK 35+, if the caller identity is personal profile, then * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa. * - * <p>If the caller needs to see all subscriptions across user profiles, - * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission - * may be required as documented on the each API. - * */ @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) @@ -1512,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); } /** @@ -1815,9 +1817,6 @@ public class SubscriptionManager { * Then for SDK 35+, if the caller identity is personal profile, then this will return * subscription 1 only and vice versa. * - * <p>If the caller needs to see all subscriptions across user profiles, - * use {@link #createForAllUserProfiles} to convert this instance to see all. - * * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by * {@link SubscriptionInfo#getSubscriptionId}. * @@ -2085,9 +2084,6 @@ public class SubscriptionManager { * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as * it does today. * - * <p>If the caller needs to see all subscriptions across user profiles, - * use {@link #createForAllUserProfiles} to convert this instance to see all. - * * @return The current number of active subscriptions. * * @see #getActiveSubscriptionInfoList() diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index b96914e59c0e..f206987ddbf6 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -18009,6 +18009,64 @@ public class TelephonyManager { } /** + * Enable or disable notifications sent for cellular identifier disclosure events. + * + * Disclosure events are defined as instances where a device has sent a cellular identifier + * on the Non-access stratum (NAS) before a security context is established. As a result the + * identifier is sent in the clear, which has privacy implications for the user. + * + * @param enable if notifications about disclosure events should be enabled + * @throws IllegalStateException if the Telephony process is not currently available + * @throws SecurityException if the caller does not have the required privileges + * @throws UnsupportedOperationException if the modem does not support this feature. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY) + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + @SystemApi + public void enableCellularIdentifierDisclosureNotifications(boolean enable) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.enableCellularIdentifierDisclosureNotifications(enable); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "enableCellularIdentifierDisclosureNotifications RemoteException", ex); + ex.rethrowFromSystemServer(); + } + } + + /** + * Get whether or not cellular identifier disclosure notifications are enabled. + * + * @throws IllegalStateException if the Telephony process is not currently available + * @throws SecurityException if the caller does not have the required privileges + * @throws UnsupportedOperationException if the modem does not support this feature. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_ENABLE_IDENTIFIER_DISCLOSURE_TRANSPARENCY) + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SystemApi + public boolean isCellularIdentifierDisclosureNotificationEnabled() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.isCellularIdentifierDisclosureNotificationEnabled(); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "isCellularIdentifierDisclosureNotificationEnabled RemoteException", ex); + ex.rethrowFromSystemServer(); + } + return false; + } + + /** * Get current cell broadcast message identifier ranges. * * @throws SecurityException if the caller does not have the required permission diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java index 16d765455d21..2fec423d1d65 100644 --- a/telephony/java/android/telephony/satellite/NtnSignalStrength.java +++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java @@ -86,6 +86,9 @@ public final class NtnSignalStrength implements Parcelable { readFromParcel(in); } + /** + * Returns notified non-terrestrial network signal strength level. + */ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NtnSignalStrengthLevel public int getLevel() { return mLevel; diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 71786b308937..e09bd201f93e 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -34,6 +34,7 @@ import android.os.ICancellationSignal; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.ServiceSpecificException; import android.telephony.SubscriptionManager; import android.telephony.TelephonyCallback; import android.telephony.TelephonyFrameworkInitializer; @@ -1919,7 +1920,6 @@ public final class SatelliteManager { */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) - @NonNull public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1962,6 +1962,8 @@ public final class SatelliteManager { /** * Registers for NTN signal strength changed from satellite modem. + * If the registration operation is not successful, a {@link SatelliteException} that contains + * {@link SatelliteResult} will be thrown. * * <p> * Note: This API is specifically designed for OEM enabled satellite connectivity only. @@ -1973,16 +1975,14 @@ public final class SatelliteManager { * @param executor The executor on which the callback will be called. * @param callback The callback to handle the NTN signal strength changed event. * - * @return The {@link SatelliteResult} result of the operation. - * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. + * @throws SatelliteException if the callback registration operation fails. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) - @SatelliteResult public int registerForNtnSignalStrengthChanged( - @NonNull @CallbackExecutor Executor executor, - @NonNull NtnSignalStrengthCallback callback) { + public void registerForNtnSignalStrengthChanged(@NonNull @CallbackExecutor Executor executor, + @NonNull NtnSignalStrengthCallback callback) throws SatelliteException { Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -1999,16 +1999,18 @@ public final class SatelliteManager { ntnSignalStrength))); } }; + telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback); sNtnSignalStrengthCallbackMap.put(callback, internalCallback); - return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback); } else { throw new IllegalStateException("Telephony service is null."); } + } catch (ServiceSpecificException ex) { + logd("registerForNtnSignalStrengthChanged() registration fails: " + ex.errorCode); + throw new SatelliteException(ex.errorCode); } catch (RemoteException ex) { loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex); ex.rethrowFromSystemServer(); } - return SATELLITE_RESULT_REQUEST_FAILED; } /** @@ -2025,6 +2027,8 @@ public final class SatelliteManager { * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}. * * @throws SecurityException if the caller doesn't have required permission. + * @throws IllegalArgumentException if the callback is not valid or has already been + * unregistered. * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @@ -2041,6 +2045,7 @@ public final class SatelliteManager { telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback); } else { loge("unregisterForNtnSignalStrengthChanged: No internal callback."); + throw new IllegalArgumentException("callback is not valid"); } } else { throw new IllegalStateException("Telephony service is null."); diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 4c53f8ab9bca..397fb2d6db96 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3101,16 +3101,21 @@ interface ITelephony { void requestNtnSignalStrength(int subId, in ResultReceiver receiver); /** - * Registers for NTN signal strength changed from satellite modem. + * Registers for NTN signal strength changed from satellite modem. If the registration operation + * is not successful, a {@link SatelliteException} that contains {@link SatelliteResult} will be + * thrown. * * @param subId The subId of the subscription to request for. - * @param callback The callback to handle the NTN signal strength changed event. - * - * @return The {@link SatelliteResult} result of the operation. + * @param callback The callback to handle the NTN signal strength changed event. If the + * operation is successful, {@link NtnSignalStrengthCallback#onNtnSignalStrengthChanged( + * NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of + * {@link NtnSignalStrength.NtnSignalStrengthLevel} when the signal strength of non-terrestrial + * network has changed. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + "android.Manifest.permission.SATELLITE_COMMUNICATION)") - int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback); + void registerForNtnSignalStrengthChanged(int subId, + in INtnSignalStrengthCallback callback); /** * Unregisters for NTN signal strength changed from satellite modem. @@ -3159,4 +3164,37 @@ interface ITelephony { * @return {@code true} if the operation is successful, {@code false} otherwise. */ boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode); + + /** + * Enable or disable notifications sent for cellular identifier disclosure events. + * + * Disclosure events are defined as instances where a device has sent a cellular identifier + * on the Non-access stratum (NAS) before a security context is established. As a result the + * identifier is sent in the clear, which has privacy implications for the user. + * + * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE</p> + * + * @param enabled if notifications about disclosure events should be enabled + * @throws IllegalStateException if the Telephony process is not currently available + * @throws SecurityException if the caller does not have the required privileges + * @throws UnsupportedOperationException if the modem does not support this feature. + * @hide + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.MODIFY_PHONE_STATE)") + void enableCellularIdentifierDisclosureNotifications(boolean enable); + + /** + * Get whether or not cellular identifier disclosure notifications are enabled. + * + * <p>Requires permission: android.Manifest.READ_PRIVILEGED_PHONE_STATE</p> + * + * @throws IllegalStateException if the Telephony process is not currently available + * @throws SecurityException if the caller does not have the required privileges + * @throws UnsupportedOperationException if the modem does not support this feature. + * @hide + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)") + boolean isCellularIdentifierDisclosureNotificationEnabled(); } diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp index 15a6a207d6d1..6fcdf1c77704 100644 --- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp +++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp @@ -27,4 +27,7 @@ android_test { name: "AaptSymlinkTest", sdk_version: "current", use_resource_processor: false, + compile_data: [ + "targets/*", + ], } 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 new file mode 100644 index 000000000000..a1356237e5a4 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java @@ -0,0 +1,371 @@ +/* + * 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.hoststubgen.nativesubstitution; + +import android.os.BadParcelableException; +import android.os.Parcel; + +import java.util.Arrays; +import java.util.HashMap; + +/** + * Native implementation substitutions for the LongArrayMultiStateCounter class. + */ +public class LongArrayMultiStateCounter_host { + + /** + * A reimplementation of {@link com.android.internal.os.LongArrayMultiStateCounter}, only in + * Java instead of native. The majority of the code (in C++) can be found in + * /frameworks/native/libs/battery/MultiStateCounter.h + */ + private static class LongArrayMultiStateCounterRavenwood { + private final int mStateCount; + private final int mArrayLength; + private int mCurrentState; + private long mLastStateChangeTimestampMs = -1; + private long mLastUpdateTimestampMs = -1; + private boolean mEnabled = true; + + private static class State { + private long mTimeInStateSinceUpdate; + private long[] mCounter; + } + + private final State[] mStates; + private final long[] mValues; + private final long[] mDelta; + + LongArrayMultiStateCounterRavenwood(int stateCount, int arrayLength) { + mStateCount = stateCount; + mArrayLength = arrayLength; + mStates = new State[stateCount]; + for (int i = 0; i < mStateCount; i++) { + mStates[i] = new State(); + mStates[i].mCounter = new long[mArrayLength]; + } + mValues = new long[mArrayLength]; + mDelta = new long[mArrayLength]; + } + + public void setEnabled(boolean enabled, long timestampMs) { + if (enabled == mEnabled) { + return; + } + + if (!enabled) { + setState(mCurrentState, timestampMs); + mEnabled = false; + } else { + if (timestampMs < mLastUpdateTimestampMs) { + timestampMs = mLastUpdateTimestampMs; + } + + if (mLastStateChangeTimestampMs >= 0) { + mLastStateChangeTimestampMs = timestampMs; + } + mEnabled = true; + } + } + + public void setState(int state, long timestampMs) { + if (mEnabled && mLastStateChangeTimestampMs >= 0 && mLastUpdateTimestampMs >= 0) { + if (timestampMs < mLastUpdateTimestampMs) { + timestampMs = mLastUpdateTimestampMs; + } + + if (timestampMs >= mLastStateChangeTimestampMs) { + mStates[mCurrentState].mTimeInStateSinceUpdate += + timestampMs - mLastStateChangeTimestampMs; + } else { + for (int i = 0; i < mStateCount; i++) { + mStates[i].mTimeInStateSinceUpdate = 0; + } + } + } + mCurrentState = state; + 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) { + timestampMs = mLastStateChangeTimestampMs; + } + + setState(mCurrentState, timestampMs); + + if (mLastUpdateTimestampMs >= 0) { + if (timestampMs > mLastUpdateTimestampMs) { + if (delta(mValues, values, mDelta)) { + long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs; + for (int i = 0; i < mStateCount; i++) { + long timeInState = mStates[i].mTimeInStateSinceUpdate; + if (timeInState > 0) { + add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate); + mStates[i].mTimeInStateSinceUpdate = 0; + } + } + } else { + throw new RuntimeException(); + } + } else if (timestampMs < mLastUpdateTimestampMs) { + throw new RuntimeException(); + } + } + } + System.arraycopy(values, 0, mValues, 0, mArrayLength); + mLastUpdateTimestampMs = timestampMs; + } + + public void incrementValues(long[] delta, long timestampMs) { + long[] values = Arrays.copyOf(mValues, mValues.length); + for (int i = 0; i < mArrayLength; i++) { + values[i] += delta[i]; + } + updateValue(values, timestampMs); + } + + public void getValues(long[] values, int state) { + System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength); + } + + public void reset() { + mLastStateChangeTimestampMs = -1; + mLastUpdateTimestampMs = -1; + for (int i = 0; i < mStateCount; i++) { + mStates[i].mTimeInStateSinceUpdate = 0; + Arrays.fill(mStates[i].mCounter, 0); + } + } + + public void writeToParcel(Parcel parcel) { + parcel.writeInt(mStateCount); + parcel.writeInt(mArrayLength); + for (int i = 0; i < mStateCount; i++) { + parcel.writeLongArray(mStates[i].mCounter); + } + } + + public void initFromParcel(Parcel parcel) { + try { + for (int i = 0; i < mStateCount; i++) { + parcel.readLongArray(mStates[i].mCounter); + } + } catch (Exception e) { + throw new BadParcelableException(e); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int state = 0; state < mStateCount; state++) { + if (state != 0) { + sb.append(", "); + } + sb.append(state).append(": {"); + for (int i = 0; i < mStates[state].mCounter.length; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(mStates[state].mCounter[i]); + } + sb.append("}"); + } + sb.append("]"); + if (mLastUpdateTimestampMs >= 0) { + sb.append(" updated: ").append(mLastUpdateTimestampMs); + } + if (mLastStateChangeTimestampMs >= 0) { + sb.append(" currentState: ").append(mCurrentState); + if (mLastStateChangeTimestampMs > mLastUpdateTimestampMs) { + sb.append(" stateChanged: ").append(mLastStateChangeTimestampMs); + } + } else { + sb.append(" currentState: none"); + } + return sb.toString(); + } + + private boolean delta(long[] values1, long[] values2, long[] delta) { + if (delta.length != mArrayLength) { + throw new RuntimeException(); + } + + boolean is_delta_valid = true; + for (int i = 0; i < mArrayLength; i++) { + if (values2[i] >= values1[i]) { + delta[i] = values2[i] - values1[i]; + } else { + delta[i] = 0; + is_delta_valid = false; + } + } + + return is_delta_valid; + } + + private void add(long[] counter, long[] delta, long numerator, long denominator) { + if (numerator != denominator) { + for (int i = 0; i < mArrayLength; i++) { + counter[i] += delta[i] * numerator / denominator; + } + } else { + for (int i = 0; i < mArrayLength; i++) { + counter[i] += delta[i]; + } + } + } + } + + public static class LongArrayContainer_host { + private static final HashMap<Long, long[]> sInstances = new HashMap<>(); + private static long sNextId = 1; + + public static long native_init(int arrayLength) { + long[] array = new long[arrayLength]; + long instanceId = sNextId++; + sInstances.put(instanceId, array); + return instanceId; + } + + static long[] getInstance(long instanceId) { + return sInstances.get(instanceId); + } + + public static void native_setValues(long instanceId, long[] values) { + System.arraycopy(values, 0, getInstance(instanceId), 0, values.length); + } + + public static void native_getValues(long instanceId, long[] values) { + System.arraycopy(getInstance(instanceId), 0, values, 0, values.length); + } + + public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) { + long[] values = getInstance(instanceId); + + boolean nonZero = false; + Arrays.fill(array, 0); + + for (int i = 0; i < values.length; i++) { + int index = indexMap[i]; + if (index < 0 || index >= array.length) { + throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, " + + (array.length - 1) + "]"); + } + if (values[i] != 0) { + array[index] += values[i]; + nonZero = true; + } + } + return nonZero; + } + } + + private static final HashMap<Long, LongArrayMultiStateCounterRavenwood> sInstances = + new HashMap<>(); + private static long sNextId = 1; + + public static long native_init(int stateCount, int arrayLength) { + LongArrayMultiStateCounterRavenwood instance = new LongArrayMultiStateCounterRavenwood( + stateCount, arrayLength); + long instanceId = sNextId++; + sInstances.put(instanceId, instance); + return instanceId; + } + + private static LongArrayMultiStateCounterRavenwood getInstance(long instanceId) { + return sInstances.get(instanceId); + } + + public static void native_setEnabled(long instanceId, boolean enabled, + long timestampMs) { + getInstance(instanceId).setEnabled(enabled, timestampMs); + } + + public static int native_getStateCount(long instanceId) { + return getInstance(instanceId).mStateCount; + } + + public static int native_getArrayLength(long instanceId) { + 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( + LongArrayContainer_host.getInstance(containerInstanceId), timestampMs); + } + + public static void native_setState(long instanceId, int state, long timestampMs) { + getInstance(instanceId).setState(state, timestampMs); + } + + public static void native_incrementValues(long instanceId, long containerInstanceId, + long timestampMs) { + getInstance(instanceId).incrementValues( + LongArrayContainer_host.getInstance(containerInstanceId), timestampMs); + } + + public static void native_getCounts(long instanceId, long containerInstanceId, int state) { + getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId), + state); + } + + public static void native_reset(long instanceId) { + getInstance(instanceId).reset(); + } + + public static void native_writeToParcel(long instanceId, Parcel parcel, int flags) { + getInstance(instanceId).writeToParcel(parcel); + } + + public static long native_initFromParcel(Parcel parcel) { + int stateCount = parcel.readInt(); + if (stateCount < 0 || stateCount > 0xEFFF) { + throw new BadParcelableException("stateCount out of range"); + } + // LongArrayMultiStateCounter.cpp uses AParcel, which throws on out-of-data. + if (parcel.dataPosition() >= parcel.dataSize()) { + throw new RuntimeException("Bad parcel"); + } + int arrayLength = parcel.readInt(); + if (parcel.dataPosition() >= parcel.dataSize()) { + throw new RuntimeException("Bad parcel"); + } + long instanceId = native_init(stateCount, arrayLength); + getInstance(instanceId).initFromParcel(parcel); + if (parcel.dataPosition() > parcel.dataSize()) { + throw new RuntimeException("Bad parcel"); + } + return instanceId; + } + + public static String native_toString(long instanceId) { + return getInstance(instanceId).toString(); + } +} 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); |