diff options
508 files changed, 12842 insertions, 4752 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 85323c35aad7..6975b557413c 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -171,6 +171,7 @@ java_aconfig_library { // DeviceStateManager aconfig_declarations { name: "android.hardware.devicestate.feature.flags-aconfig", + exportable: true, package: "android.hardware.devicestate.feature.flags", srcs: ["core/java/android/hardware/devicestate/feature/*.aconfig"], } @@ -184,6 +185,7 @@ java_aconfig_library { // Input aconfig_declarations { name: "com.android.hardware.input.input-aconfig", + exportable: true, package: "com.android.hardware.input", srcs: ["core/java/android/hardware/input/*.aconfig"], } @@ -456,6 +458,7 @@ cc_aconfig_library { // Hardware aconfig_declarations { name: "android.hardware.flags-aconfig", + exportable: true, package: "android.hardware.flags", srcs: ["core/java/android/hardware/flags/*.aconfig"], } @@ -548,6 +551,7 @@ java_aconfig_library { // Media Editing aconfig_declarations { name: "com.android.media.flags.editing-aconfig", + exportable: true, package: "com.android.media.editing.flags", srcs: [ "media/java/android/media/flags/editing.aconfig", @@ -593,6 +597,7 @@ java_aconfig_library { // Media TV aconfig_declarations { name: "android.media.tv.flags-aconfig", + exportable: true, package: "android.media.tv.flags", srcs: ["media/java/android/media/tv/flags/media_tv.aconfig"], } @@ -606,6 +611,7 @@ java_aconfig_library { // OnDeviceIntelligence aconfig_declarations { name: "android.app.ondeviceintelligence-aconfig", + exportable: true, package: "android.app.ondeviceintelligence.flags", srcs: ["core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig"], } @@ -657,6 +663,7 @@ cc_aconfig_library { // Biometrics aconfig_declarations { name: "android.hardware.biometrics.flags-aconfig", + exportable: true, package: "android.hardware.biometrics", srcs: ["core/java/android/hardware/biometrics/flags.aconfig"], } @@ -734,6 +741,7 @@ java_aconfig_library { // Broadcast Radio aconfig_declarations { name: "android.hardware.radio.flags-aconfig", + exportable: true, package: "android.hardware.radio", srcs: ["core/java/android/hardware/radio/*.aconfig"], } @@ -768,6 +776,7 @@ java_aconfig_library { // Content Protection aconfig_declarations { name: "android.view.contentprotection.flags-aconfig", + exportable: true, package: "android.view.contentprotection.flags", srcs: ["core/java/android/view/contentprotection/flags/*.aconfig"], } @@ -794,6 +803,7 @@ java_aconfig_library { // App prediction aconfig_declarations { name: "android.service.appprediction.flags-aconfig", + exportable: true, package: "android.service.appprediction.flags", srcs: ["core/java/android/service/appprediction/flags/*.aconfig"], } @@ -807,6 +817,7 @@ java_aconfig_library { // Controls aconfig_declarations { name: "android.service.controls.flags-aconfig", + exportable: true, package: "android.service.controls.flags", srcs: ["core/java/android/service/controls/flags/*.aconfig"], } @@ -820,6 +831,7 @@ java_aconfig_library { // Voice aconfig_declarations { name: "android.service.voice.flags-aconfig", + exportable: true, package: "android.service.voice.flags", srcs: ["core/java/android/service/voice/flags/*.aconfig"], } @@ -849,6 +861,7 @@ java_aconfig_library { // Companion aconfig_declarations { name: "android.companion.flags-aconfig", + exportable: true, package: "android.companion", srcs: ["core/java/android/companion/*.aconfig"], } @@ -862,6 +875,7 @@ java_aconfig_library { // Networking aconfig_declarations { name: "android.net.platform.flags-aconfig", + exportable: true, package: "android.net.platform.flags", srcs: ["core/java/android/net/flags.aconfig"], visibility: [":__subpackages__"], @@ -870,6 +884,7 @@ aconfig_declarations { // Thread network aconfig_declarations { name: "com.android.net.thread.platform.flags-aconfig", + exportable: true, package: "com.android.net.thread.platform.flags", srcs: ["core/java/android/net/thread/flags.aconfig"], } @@ -967,6 +982,7 @@ java_aconfig_library { aconfig_declarations { name: "framework-jobscheduler-job.flags-aconfig", package: "android.app.job", + exportable: true, srcs: ["apex/jobscheduler/framework/aconfig/job.aconfig"], } @@ -1032,6 +1048,7 @@ java_aconfig_library { // Smartspace aconfig_declarations { name: "android.app.smartspace.flags-aconfig", + exportable: true, package: "android.app.smartspace.flags", srcs: ["core/java/android/app/smartspace/flags.aconfig"], } @@ -1065,6 +1082,7 @@ java_aconfig_library { // USB aconfig_declarations { name: "android.hardware.usb.flags-aconfig", + exportable: true, package: "android.hardware.usb.flags", srcs: ["core/java/android/hardware/usb/flags/*.aconfig"], } @@ -1145,6 +1163,7 @@ java_aconfig_library { // Provider aconfig_declarations { name: "android.provider.flags-aconfig", + exportable: true, package: "android.provider", srcs: ["core/java/android/provider/*.aconfig"], } @@ -1165,6 +1184,7 @@ java_aconfig_library { // Speech aconfig_declarations { name: "android.speech.flags-aconfig", + exportable: true, package: "android.speech.flags", srcs: ["core/java/android/speech/flags/*.aconfig"], } @@ -1185,6 +1205,7 @@ java_aconfig_library { // Content aconfig_declarations { name: "android.content.flags-aconfig", + exportable: true, package: "android.content.flags", srcs: ["core/java/android/content/flags/flags.aconfig"], } @@ -1211,6 +1232,7 @@ java_aconfig_library { // CrashRecovery Module aconfig_declarations { name: "android.crashrecovery.flags-aconfig", + exportable: true, package: "android.crashrecovery.flags", srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"], } @@ -1249,6 +1271,7 @@ java_aconfig_library { // Wearable Sensing aconfig_declarations { name: "android.app.wearable.flags-aconfig", + exportable: true, package: "android.app.wearable", srcs: ["core/java/android/app/wearable/*.aconfig"], } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 096238aeda7c..012ede274bc1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1737,17 +1737,6 @@ class JobConcurrencyManager { continue; } - if (!nextPending.isReady()) { - // This could happen when the job count reached its quota, the constrains - // for the job has been updated but hasn't been removed from the pending - // queue yet. - if (DEBUG) { - Slog.w(TAG, "Pending+not ready job: " + nextPending); - } - pendingJobQueue.remove(nextPending); - continue; - } - if (DEBUG && isSimilarJobRunningLocked(nextPending)) { Slog.w(TAG, "Already running similar job to: " + nextPending); } 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 cfbfa5dce399..3c9648b20003 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 @@ -512,7 +512,7 @@ public final class QuotaController extends StateController { /** An app has reached its quota. The message should contain a {@link UserPackage} object. */ @VisibleForTesting - static final int MSG_REACHED_TIME_QUOTA = 0; + static final int MSG_REACHED_QUOTA = 0; /** Drop any old timing sessions. */ private static final int MSG_CLEAN_UP_SESSIONS = 1; /** Check if a package is now within its quota. */ @@ -524,7 +524,7 @@ public final class QuotaController extends StateController { * object. */ @VisibleForTesting - static final int MSG_REACHED_EJ_TIME_QUOTA = 4; + static final int MSG_REACHED_EJ_QUOTA = 4; /** * Process a new {@link UsageEvents.Event}. The event will be the message's object and the * userId will the first arg. @@ -533,11 +533,6 @@ public final class QuotaController extends StateController { /** A UID's free quota grace period has ended. */ @VisibleForTesting static final int MSG_END_GRACE_PERIOD = 6; - /** - * An app has reached its job count quota. The message should contain a {@link UserPackage} - * object. - */ - static final int MSG_REACHED_COUNT_QUOTA = 7; public QuotaController(@NonNull JobSchedulerService service, @NonNull BackgroundJobsController backgroundJobsController, @@ -879,37 +874,17 @@ public final class QuotaController extends StateController { } @VisibleForTesting - @GuardedBy("mLock") boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) { final int standbyBucket = jobStatus.getEffectiveStandbyBucket(); // A job is within quota if one of the following is true: // 1. it was started while the app was in the TOP state // 2. the app is currently in the foreground // 3. the app overall is within its quota - if (jobStatus.shouldTreatAsUserInitiatedJob() + return jobStatus.shouldTreatAsUserInitiatedJob() || isTopStartedJobLocked(jobStatus) - || isUidInForeground(jobStatus.getSourceUid())) { - return true; - } - - if (standbyBucket == NEVER_INDEX) return false; - - if (isQuotaFreeLocked(standbyBucket)) return true; - - final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(), - jobStatus.getSourcePackageName(), standbyBucket); - if (!(getRemainingExecutionTimeLocked(stats) > 0)) { - // Out of execution time quota. - return false; - } - - if (mService.isCurrentlyRunningLocked(jobStatus)) { - // if job is running, considered as in quota so it can keep running. - return true; - } - - // Check if the app is within job count quota. - return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats); + || isUidInForeground(jobStatus.getSourceUid()) + || isWithinQuotaLocked( + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); } @GuardedBy("mLock") @@ -934,11 +909,12 @@ public final class QuotaController extends StateController { ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); // TODO: use a higher minimum remaining time for jobs with MINIMUM priority return getRemainingExecutionTimeLocked(stats) > 0 - && isUnderJobCountQuotaLocked(stats) - && isUnderSessionCountQuotaLocked(stats); + && isUnderJobCountQuotaLocked(stats, standbyBucket) + && isUnderSessionCountQuotaLocked(stats, standbyBucket); } - private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) { + private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats, + final int standbyBucket) { final long now = sElapsedRealtimeClock.millis(); final boolean isUnderAllowedTimeQuota = (stats.jobRateLimitExpirationTimeElapsed <= now @@ -947,7 +923,8 @@ public final class QuotaController extends StateController { && stats.bgJobCountInWindow < stats.jobCountLimit; } - private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) { + private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats, + final int standbyBucket) { final long now = sElapsedRealtimeClock.millis(); final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow); @@ -1472,7 +1449,6 @@ public final class QuotaController extends StateController { stats.jobCountInRateLimitingWindow = 0; } stats.jobCountInRateLimitingWindow += count; - stats.bgJobCountInWindow += count; } } @@ -1707,11 +1683,10 @@ public final class QuotaController extends StateController { changedJobs.add(js); } } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX - && realStandbyBucket == js.getEffectiveStandbyBucket() - && !mService.isCurrentlyRunningLocked(js)) { + && realStandbyBucket == js.getEffectiveStandbyBucket()) { // An app in the ACTIVE bucket may be out of quota while the job could be in quota // for some reason. Therefore, avoid setting the real value here and check each job - // individually. Running job need to determine its own quota status as well. + // individually. if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) { changedJobs.add(js); } @@ -1830,8 +1805,9 @@ public final class QuotaController extends StateController { } ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); - final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats); - final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats); + final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket); + final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats, + standbyBucket); final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName); final boolean inRegularQuota = @@ -2150,11 +2126,6 @@ public final class QuotaController extends StateController { mBgJobCount++; if (mRegularJobTimer) { incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1); - final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId, - mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false); - if (stats.bgJobCountInWindow >= stats.jobCountLimit) { - mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget(); - } } if (mRunningBgJobs.size() == 1) { // Started tracking the first job. @@ -2286,6 +2257,7 @@ public final class QuotaController extends StateController { // repeatedly plugged in and unplugged, or an app changes foreground state // very frequently, the job count for a package may be artificially high. mBgJobCount = mRunningBgJobs.size(); + if (mRegularJobTimer) { incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount); // Starting the timer means that all cached execution stats are now @@ -2312,8 +2284,7 @@ public final class QuotaController extends StateController { return; } Message msg = mHandler.obtainMessage( - mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, - mPkg); + mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg); final long timeRemainingMs = mRegularJobTimer ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName) : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName); @@ -2330,7 +2301,7 @@ public final class QuotaController extends StateController { private void cancelCutoff() { mHandler.removeMessages( - mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg); + mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg); } public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { @@ -2586,7 +2557,7 @@ public final class QuotaController extends StateController { break; default: if (DEBUG) { - Slog.d(TAG, "Dropping usage event " + event.getEventType()); + Slog.d(TAG, "Dropping event " + event.getEventType()); } break; } @@ -2695,7 +2666,7 @@ public final class QuotaController extends StateController { public void handleMessage(Message msg) { synchronized (mLock) { switch (msg.what) { - case MSG_REACHED_TIME_QUOTA: { + case MSG_REACHED_QUOTA: { UserPackage pkg = (UserPackage) msg.obj; if (DEBUG) { Slog.d(TAG, "Checking if " + pkg + " has reached its quota."); @@ -2714,7 +2685,7 @@ public final class QuotaController extends StateController { // This could potentially happen if an old session phases out while a // job is currently running. // Reschedule message - Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg); + Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg); timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, pkg.packageName); if (DEBUG) { @@ -2724,7 +2695,7 @@ public final class QuotaController extends StateController { } break; } - case MSG_REACHED_EJ_TIME_QUOTA: { + case MSG_REACHED_EJ_QUOTA: { UserPackage pkg = (UserPackage) msg.obj; if (DEBUG) { Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota."); @@ -2742,7 +2713,7 @@ public final class QuotaController extends StateController { // This could potentially happen if an old session phases out while a // job is currently running. // Reschedule message - Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg); + Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg); timeRemainingMs = getTimeUntilEJQuotaConsumedLocked( pkg.userId, pkg.packageName); if (DEBUG) { @@ -2752,18 +2723,6 @@ public final class QuotaController extends StateController { } break; } - case MSG_REACHED_COUNT_QUOTA: { - UserPackage pkg = (UserPackage) msg.obj; - if (DEBUG) { - Slog.d(TAG, pkg + " has reached its count quota."); - } - - mStateChangedListener.onControllerStateChanged( - maybeUpdateConstraintForPkgLocked( - sElapsedRealtimeClock.millis(), - pkg.userId, pkg.packageName)); - break; - } case MSG_CLEAN_UP_SESSIONS: if (DEBUG) { Slog.d(TAG, "Cleaning up timing sessions."); diff --git a/api/coverage/tools/Android.bp b/api/coverage/tools/Android.bp index 3e169120dc48..caaca99bdc45 100644 --- a/api/coverage/tools/Android.bp +++ b/api/coverage/tools/Android.bp @@ -30,3 +30,24 @@ java_library_host { type: "full", }, } + +java_test_host { + name: "extract-flagged-apis-test", + srcs: ["ExtractFlaggedApisTest.kt"], + libs: [ + "extract_flagged_apis_proto", + "junit", + "libprotobuf-java-full", + ], + static_libs: [ + "truth", + "truth-liteproto-extension", + "truth-proto-extension", + ], + data: [ + ":extract-flagged-apis", + ], + test_options: { + unit_test: true, + }, +} diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt index d5adfd09b994..5178f09f0301 100644 --- a/api/coverage/tools/ExtractFlaggedApis.kt +++ b/api/coverage/tools/ExtractFlaggedApis.kt @@ -28,12 +28,10 @@ fun main(args: Array<String>) { val builder = FlagApiMap.newBuilder() for (pkg in cb.getPackages().packages) { val packageName = pkg.qualifiedName() - pkg.allClasses() - .filter { it.methods().size > 0 } - .forEach { - extractFlaggedApisFromClass(it, it.methods(), packageName, builder) - extractFlaggedApisFromClass(it, it.constructors(), packageName, builder) - } + pkg.allClasses().forEach { + extractFlaggedApisFromClass(it, it.methods(), packageName, builder) + extractFlaggedApisFromClass(it, it.constructors(), packageName, builder) + } } val flagApiMap = builder.build() FileWriter(args[1]).use { it.write(flagApiMap.toString()) } @@ -45,6 +43,7 @@ fun extractFlaggedApisFromClass( packageName: String, builder: FlagApiMap.Builder ) { + if (methods.isEmpty()) return val classFlag = classItem.modifiers .findAnnotation("android.annotation.FlaggedApi") diff --git a/api/coverage/tools/ExtractFlaggedApisTest.kt b/api/coverage/tools/ExtractFlaggedApisTest.kt new file mode 100644 index 000000000000..ee5aaf15cb57 --- /dev/null +++ b/api/coverage/tools/ExtractFlaggedApisTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.platform.coverage + +import com.google.common.truth.extensions.proto.ProtoTruth.assertThat +import com.google.protobuf.TextFormat +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class ExtractFlaggedApisTest { + + companion object { + const val COMMAND = "java -jar extract-flagged-apis.jar %s %s" + } + + private var apiTextFile: Path = Files.createTempFile("current", ".txt") + private var flagToApiMap: Path = Files.createTempFile("flag_api_map", ".textproto") + + @Before + fun setup() { + apiTextFile = Files.createTempFile("current", ".txt") + flagToApiMap = Files.createTempFile("flag_api_map", ".textproto") + } + + @After + fun cleanup() { + Files.deleteIfExists(apiTextFile) + Files.deleteIfExists(flagToApiMap) + } + + @Test + fun extractFlaggedApis_onlyMethodFlag_useMethodFlag() { + val apiText = + """ + // Signature format: 2.0 + package android.net.ipsec.ike { + public final class IkeSession implements java.lang.AutoCloseable { + method @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public void dump(@NonNull java.io.PrintWriter); + } + } + """ + .trimIndent() + Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND) + + val process = Runtime.getRuntime().exec(createCommand()) + process.waitFor() + + val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8) + val result = TextFormat.parse(content, FlagApiMap::class.java) + + val expected = FlagApiMap.newBuilder() + val api = + JavaMethod.newBuilder() + .setPackageName("android.net.ipsec.ike") + .setClassName("IkeSession") + .setMethodName("dump") + api.addParameters("java.io.PrintWriter") + addFlaggedApi(expected, api, "com.android.ipsec.flags.dumpsys_api") + assertThat(result).isEqualTo(expected.build()) + } + + @Test + fun extractFlaggedApis_onlyClassFlag_useClassFlag() { + val apiText = + """ + // Signature format: 2.0 + package android.net.ipsec.ike { + @FlaggedApi("com.android.ipsec.flags.dumpsys_api") public final class IkeSession implements java.lang.AutoCloseable { + method public void dump(@NonNull java.io.PrintWriter); + } + } + """ + .trimIndent() + Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND) + + val process = Runtime.getRuntime().exec(createCommand()) + process.waitFor() + + val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8) + val result = TextFormat.parse(content, FlagApiMap::class.java) + + val expected = FlagApiMap.newBuilder() + val api = + JavaMethod.newBuilder() + .setPackageName("android.net.ipsec.ike") + .setClassName("IkeSession") + .setMethodName("dump") + api.addParameters("java.io.PrintWriter") + addFlaggedApi(expected, api, "com.android.ipsec.flags.dumpsys_api") + assertThat(result).isEqualTo(expected.build()) + } + + @Test + fun extractFlaggedApis_flaggedConstructorsAreFlaggedApis() { + val apiText = + """ + // Signature format: 2.0 + package android.app.pinner { + @FlaggedApi("android.app.pinner_service_client_api") public class PinnerServiceClient { + ctor @FlaggedApi("android.app.pinner_service_client_api") public PinnerServiceClient(); + } + } + """ + .trimIndent() + Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND) + + val process = Runtime.getRuntime().exec(createCommand()) + process.waitFor() + + val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8) + val result = TextFormat.parse(content, FlagApiMap::class.java) + + val expected = FlagApiMap.newBuilder() + val api = + JavaMethod.newBuilder() + .setPackageName("android.app.pinner") + .setClassName("PinnerServiceClient") + .setMethodName("PinnerServiceClient") + addFlaggedApi(expected, api, "android.app.pinner_service_client_api") + assertThat(result).isEqualTo(expected.build()) + } + + private fun addFlaggedApi(builder: FlagApiMap.Builder, api: JavaMethod.Builder, flag: String) { + if (builder.containsFlagToApi(flag)) { + val updatedApis = + builder.getFlagToApiOrThrow(flag).toBuilder().addJavaMethods(api).build() + builder.putFlagToApi(flag, updatedApis) + } else { + val apis = FlaggedApis.newBuilder().addJavaMethods(api).build() + builder.putFlagToApi(flag, apis) + } + } + + private fun createCommand(): Array<String> { + val command = + String.format(COMMAND, apiTextFile.toAbsolutePath(), flagToApiMap.toAbsolutePath()) + return command.split(" ").toTypedArray() + } +} diff --git a/core/api/current.txt b/core/api/current.txt index 13d5e03da7ed..c189a24c84ae 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -54479,7 +54479,6 @@ package android.view { field @FlaggedApi("com.android.window.flags.cover_display_opt_in") public static final int COMPAT_SMALL_COVER_SCREEN_OPT_IN = 1; // 0x1 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 @FlaggedApi("com.android.window.flags.untrusted_embedding_state_sharing") public static final String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING = "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING"; field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"; field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3b988e195597..35ab5f014815 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -589,6 +589,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int); method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs(); + method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public void forceSetMaxPolicyStorageLimit(int); method public void forceUpdateUserSetupComplete(int); method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages(); method @Deprecated public int getDeviceOwnerType(@NonNull android.content.ComponentName); @@ -599,6 +600,7 @@ package android.app.admin { method public long getLastSecurityLogRetrievalTime(); method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps(); + method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public int getPolicySizeForAdmin(@NonNull android.app.admin.EnforcingAdmin); method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged(); @@ -667,6 +669,10 @@ package android.app.admin { field @NonNull public static final android.app.admin.DpcAuthority DPC_AUTHORITY; } + public final class EnforcingAdmin implements android.os.Parcelable { + ctor @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") public EnforcingAdmin(@NonNull String, @NonNull android.app.admin.Authority, @NonNull android.os.UserHandle, @Nullable android.content.ComponentName); + } + public final class FlagUnion extends android.app.admin.ResolutionMechanism<java.lang.Integer> { method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3575545e202d..f64418587918 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2608,7 +2608,14 @@ public final class ActivityThread extends ClientTransactionHandler break; case EXECUTE_TRANSACTION: final ClientTransaction transaction = (ClientTransaction) msg.obj; - mTransactionExecutor.execute(transaction); + final ClientTransactionListenerController controller = + ClientTransactionListenerController.getInstance(); + controller.onClientTransactionStarted(); + try { + mTransactionExecutor.execute(transaction); + } finally { + controller.onClientTransactionFinished(); + } if (isSystem()) { // Client transactions inside system process are recycled on the client side // instead of ClientLifecycleManager to avoid being cleared before this @@ -6747,6 +6754,21 @@ public final class ActivityThread extends ClientTransactionHandler void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r, @NonNull Configuration overrideConfig, int displayId, @NonNull ActivityWindowInfo activityWindowInfo, boolean alwaysReportChange) { + final ClientTransactionListenerController controller = + ClientTransactionListenerController.getInstance(); + final Context contextToUpdate = r.activity; + controller.onContextConfigurationPreChanged(contextToUpdate); + try { + handleActivityConfigurationChangedInner(r, overrideConfig, displayId, + activityWindowInfo, alwaysReportChange); + } finally { + controller.onContextConfigurationPostChanged(contextToUpdate); + } + } + + private void handleActivityConfigurationChangedInner(@NonNull ActivityClientRecord r, + @NonNull Configuration overrideConfig, int displayId, + @NonNull ActivityWindowInfo activityWindowInfo, boolean alwaysReportChange) { synchronized (mPendingOverrideConfigs) { final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token); if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) { diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java index 18dc1ce18baf..62a50dbbd6f7 100644 --- a/core/java/android/app/ConfigurationController.java +++ b/core/java/android/app/ConfigurationController.java @@ -21,6 +21,7 @@ import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.servertransaction.ClientTransactionListenerController; import android.content.ComponentCallbacks2; import android.content.Context; import android.content.res.CompatibilityInfo; @@ -145,6 +146,24 @@ class ConfigurationController { */ void handleConfigurationChanged(@Nullable Configuration config, @Nullable CompatibilityInfo compat) { + final ClientTransactionListenerController controller = + ClientTransactionListenerController.getInstance(); + final Context contextToUpdate = ActivityThread.currentApplication(); + controller.onContextConfigurationPreChanged(contextToUpdate); + try { + handleConfigurationChangedInner(config, compat); + } finally { + controller.onContextConfigurationPostChanged(contextToUpdate); + } + } + + /** + * Update the configuration to latest. + * @param config The new configuration. + * @param compat The new compatibility information. + */ + private void handleConfigurationChangedInner(@Nullable Configuration config, + @Nullable CompatibilityInfo compat) { int configDiff; boolean equivalent; diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 8f81ae2ae7d6..cf0641651d66 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -50,8 +50,8 @@ interface INotificationManager void cancelAllNotifications(String pkg, int userId); void clearData(String pkg, int uid, boolean fromApp); - void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, boolean isUiContext, int displayId, @nullable ITransientNotificationCallback callback); - void enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, boolean isUiContext, int displayId); + boolean enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, boolean isUiContext, int displayId, @nullable ITransientNotificationCallback callback); + boolean enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, boolean isUiContext, int displayId); void cancelToast(String pkg, IBinder token); void finishToken(String pkg, IBinder token); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index fe261bee41d8..d9e0413e5ad5 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -23,6 +23,7 @@ import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICO import static android.app.admin.DevicePolicyResources.UNDEFINED; import static android.graphics.drawable.Icon.TYPE_URI; import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; +import static android.app.Flags.cleanUpSpansAndNewLines; import static android.app.Flags.evenlyDividedCallStyleActionLayout; import static android.app.Flags.updateRankingTime; @@ -3124,9 +3125,29 @@ public class Notification implements Parcelable + " instance is a custom Parcelable and not allowed in Notification"); return cs.toString(); } + if (Flags.cleanUpSpansAndNewLines()) { + return stripStyling(cs); + } + return removeTextSizeSpans(cs); } + private static CharSequence stripStyling(@Nullable CharSequence cs) { + if (cs == null) { + return cs; + } + + return cs.toString(); + } + + private static CharSequence cleanUpNewLines(@Nullable CharSequence charSequence) { + if (charSequence == null) { + return charSequence; + } + + return charSequence.toString().replaceAll("[\r\n]+", "\n"); + } + private static CharSequence removeTextSizeSpans(CharSequence charSequence) { if (charSequence instanceof Spanned) { Spanned ss = (Spanned) charSequence; @@ -5505,7 +5526,8 @@ public class Notification implements Parcelable boolean hasSecondLine = showProgress; if (p.hasTitle()) { contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE); - contentView.setTextViewText(p.mTitleViewId, ensureColorSpanContrast(p.mTitle, p)); + contentView.setTextViewText(p.mTitleViewId, + ensureColorSpanContrastOrStripStyling(p.mTitle, p)); setTextViewColorPrimary(contentView, p.mTitleViewId, p); } else if (p.mTitleViewId != R.id.title) { // This alternate title view ID is not cleared by resetStandardTemplate @@ -5515,7 +5537,8 @@ public class Notification implements Parcelable if (p.mText != null && p.mText.length() != 0 && (!showProgress || p.mAllowTextWithProgress)) { contentView.setViewVisibility(p.mTextViewId, View.VISIBLE); - contentView.setTextViewText(p.mTextViewId, ensureColorSpanContrast(p.mText, p)); + contentView.setTextViewText(p.mTextViewId, + ensureColorSpanContrastOrStripStyling(p.mText, p)); setTextViewColorSecondary(contentView, p.mTextViewId, p); hasSecondLine = true; } else if (p.mTextViewId != R.id.text) { @@ -5804,7 +5827,7 @@ public class Notification implements Parcelable headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); } if (!TextUtils.isEmpty(headerText)) { - contentView.setTextViewText(R.id.header_text, ensureColorSpanContrast( + contentView.setTextViewText(R.id.header_text, ensureColorSpanContrastOrStripStyling( processLegacyText(headerText), p)); setTextViewColorSecondary(contentView, R.id.header_text, p); contentView.setViewVisibility(R.id.header_text, View.VISIBLE); @@ -5826,8 +5849,9 @@ public class Notification implements Parcelable return false; } if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) { - contentView.setTextViewText(R.id.header_text_secondary, ensureColorSpanContrast( - processLegacyText(p.mHeaderTextSecondary), p)); + contentView.setTextViewText(R.id.header_text_secondary, + ensureColorSpanContrastOrStripStyling( + processLegacyText(p.mHeaderTextSecondary), p)); setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); if (hasTextToLeft) { @@ -6048,7 +6072,7 @@ public class Notification implements Parcelable big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_1, - ensureColorSpanContrast(replyText[0].getText(), p)); + ensureColorSpanContrastOrStripStyling(replyText[0].getText(), p)); setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); big.setViewVisibility(R.id.notification_material_reply_progress, showSpinner ? View.VISIBLE : View.GONE); @@ -6060,7 +6084,7 @@ public class Notification implements Parcelable && p.maxRemoteInputHistory > 1) { big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_2, - ensureColorSpanContrast(replyText[1].getText(), p)); + ensureColorSpanContrastOrStripStyling(replyText[1].getText(), p)); setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) @@ -6068,7 +6092,7 @@ public class Notification implements Parcelable big.setViewVisibility( R.id.notification_material_reply_text_3, View.VISIBLE); big.setTextViewText(R.id.notification_material_reply_text_3, - ensureColorSpanContrast(replyText[2].getText(), p)); + ensureColorSpanContrastOrStripStyling(replyText[2].getText(), p)); setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); } } @@ -6500,21 +6524,37 @@ public class Notification implements Parcelable mContext, getColors(p).getBackgroundColor(), mInNightMode), R.dimen.notification_action_disabled_container_alpha); } - if (isLegacy()) { - title = ContrastColorUtil.clearColorSpans(title); + if (Flags.cleanUpSpansAndNewLines()) { + if (!isLegacy()) { + // Check for a full-length span color to use as the button fill color. + Integer fullLengthColor = getFullLengthSpanColor(title); + if (fullLengthColor != null) { + // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. + int notifBackgroundColor = getColors(p).getBackgroundColor(); + buttonFillColor = ensureButtonFillContrast( + fullLengthColor, notifBackgroundColor); + } + } } else { - // Check for a full-length span color to use as the button fill color. - Integer fullLengthColor = getFullLengthSpanColor(title); - if (fullLengthColor != null) { - // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. - int notifBackgroundColor = getColors(p).getBackgroundColor(); - buttonFillColor = ensureButtonFillContrast( - fullLengthColor, notifBackgroundColor); + if (isLegacy()) { + title = ContrastColorUtil.clearColorSpans(title); + } else { + // Check for a full-length span color to use as the button fill color. + Integer fullLengthColor = getFullLengthSpanColor(title); + if (fullLengthColor != null) { + // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. + int notifBackgroundColor = getColors(p).getBackgroundColor(); + buttonFillColor = ensureButtonFillContrast( + fullLengthColor, notifBackgroundColor); + } + // Remove full-length color spans + // and ensure text contrast with the button fill. + title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor); } - // Remove full-length color spans and ensure text contrast with the button fill. - title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor); } - final CharSequence label = ensureColorSpanContrast(title, p); + + + final CharSequence label = ensureColorSpanContrastOrStripStyling(title, p); if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) { if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) { Log.d(TAG, "new action layout enabled, gluing instead of setting text"); @@ -6554,7 +6594,7 @@ public class Notification implements Parcelable button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen); } } else { - button.setTextViewText(R.id.action0, ensureColorSpanContrast( + button.setTextViewText(R.id.action0, ensureColorSpanContrastOrStripStyling( action.title, p)); button.setTextColor(R.id.action0, getStandardActionColor(p)); } @@ -6629,6 +6669,26 @@ public class Notification implements Parcelable } /** + * @hide + */ + public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs, + StandardTemplateParams p) { + return ensureColorSpanContrastOrStripStyling(cs, getBackgroundColor(p)); + } + + /** + * @hide + */ + public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs, + int buttonFillColor) { + if (Flags.cleanUpSpansAndNewLines()) { + return stripStyling(cs); + } + + return ContrastColorUtil.ensureColorSpanContrast(cs, buttonFillColor); + } + + /** * Ensures contrast on color spans against a background color. * Note that any full-length color spans will be removed instead of being contrasted. * @@ -7853,8 +7913,9 @@ public class Notification implements Parcelable RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(), p, null /* result */); if (mSummaryTextSet) { - contentView.setTextViewText(R.id.text, mBuilder.ensureColorSpanContrast( - mBuilder.processLegacyText(mSummaryText), p)); + contentView.setTextViewText(R.id.text, + mBuilder.ensureColorSpanContrastOrStripStyling( + mBuilder.processLegacyText(mSummaryText), p)); mBuilder.setTextViewColorSecondary(contentView, R.id.text, p); contentView.setViewVisibility(R.id.text, View.VISIBLE); } @@ -8017,6 +8078,9 @@ public class Notification implements Parcelable */ public BigTextStyle bigText(CharSequence cs) { mBigText = safeCharSequence(cs); + if (Flags.cleanUpSpansAndNewLines()) { + mBigText = cleanUpNewLines(mBigText); + } return this; } @@ -8517,7 +8581,7 @@ public class Notification implements Parcelable for (int i = 0; i < N; i++) { final Message m = messages.get(i); if (ensureContrast) { - m.ensureColorContrast(backgroundColor); + m.ensureColorContrastOrStripStyling(backgroundColor); } bundles[i] = m.toBundle(); } @@ -8543,7 +8607,9 @@ public class Notification implements Parcelable } else { title = sender; } - + if (Flags.cleanUpSpansAndNewLines()) { + title = stripStyling(title); + } if (title != null) { extras.putCharSequence(EXTRA_TITLE, title); } @@ -8995,6 +9061,17 @@ public class Notification implements Parcelable } /** + * Strip styling or updates TextAppearance spans in message text. + * @hide + */ + public void ensureColorContrastOrStripStyling(int backgroundColor) { + if (Flags.cleanUpSpansAndNewLines()) { + mText = stripStyling(mText); + } else { + ensureColorContrast(backgroundColor); + } + } + /** * Updates TextAppearance spans in the message text so it has sufficient contrast * against its background. * @hide @@ -9324,7 +9401,8 @@ public class Notification implements Parcelable if (!TextUtils.isEmpty(str)) { contentView.setViewVisibility(rowIds[i], View.VISIBLE); contentView.setTextViewText(rowIds[i], - mBuilder.ensureColorSpanContrast(mBuilder.processLegacyText(str), p)); + mBuilder.ensureColorSpanContrastOrStripStyling( + mBuilder.processLegacyText(str), p)); mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p); contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); if (first) { diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 02d694436bfe..257aff04a09f 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1603,8 +1603,18 @@ public class WallpaperManager { @SetWallpaperFlags int which, boolean originalBitmap) { checkExactlyOneWallpaperFlagSet(which); try { - return sGlobals.mService.getBitmapCrops(displaySizes, which, originalBitmap, - mContext.getUserId()); + List<Rect> result = sGlobals.mService.getBitmapCrops( + displaySizes, which, originalBitmap, mContext.getUserId()); + if (result != null) return result; + // mService.getBitmapCrops returns null if the requested wallpaper is an ImageWallpaper, + // but there are no crop hints and the bitmap size is unknown to the service (this + // mostly happens for the default wallpaper). In that case, fetch the bitmap dimensions + // and use the other getBitmapCrops API with no cropHints to figure out the crops. + Rect bitmapDimensions = peekBitmapDimensions(which, true); + if (bitmapDimensions == null) return List.of(); + Point bitmapSize = new Point(bitmapDimensions.width(), bitmapDimensions.height()); + return getBitmapCrops(bitmapSize, displaySizes, null); + } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java index 51f313755e59..02e492bb06aa 100644 --- a/core/java/android/app/admin/AccountTypePolicyKey.java +++ b/core/java/android/app/admin/AccountTypePolicyKey.java @@ -54,7 +54,7 @@ public final class AccountTypePolicyKey extends PolicyKey { @TestApi public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) { super(key); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType"); } mAccountType = Objects.requireNonNull((accountType)); diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java index cb5e9861141d..c993671f4fc1 100644 --- a/core/java/android/app/admin/BundlePolicyValue.java +++ b/core/java/android/app/admin/BundlePolicyValue.java @@ -31,7 +31,7 @@ public final class BundlePolicyValue extends PolicyValue<Bundle> { public BundlePolicyValue(Bundle value) { super(value); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { PolicySizeVerifier.enforceMaxBundleFieldsLength(value); } } diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java index a957dbf132bb..a7a2f7d27e0d 100644 --- a/core/java/android/app/admin/ComponentNamePolicyValue.java +++ b/core/java/android/app/admin/ComponentNamePolicyValue.java @@ -31,7 +31,7 @@ public final class ComponentNamePolicyValue extends PolicyValue<ComponentName> { public ComponentNamePolicyValue(@NonNull ComponentName value) { super(value); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { PolicySizeVerifier.enforceMaxComponentNameLength(value); } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index ea6f45e8e201..ba91be9e9e6c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -54,6 +54,7 @@ import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; +import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED; import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED; import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED; import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; @@ -7055,9 +7056,10 @@ public class DevicePolicyManager { public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; /** - * Disable all keyguard widgets. Has no effect starting from - * {@link android.os.Build.VERSION_CODES#LOLLIPOP} since keyguard widget is only supported - * on Android versions lower than 5.0. + * Disable all keyguard widgets. Has no effect between {@link + * android.os.Build.VERSION_CODES#LOLLIPOP} and {@link + * android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} (both inclusive), since keyguard widget is + * only supported on Android versions lower than 5.0 and versions higher than 14. */ public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1 << 0; @@ -7156,7 +7158,8 @@ public class DevicePolicyManager { public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY = DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA | DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS - | DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL; + | DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL + | DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL; /** * Keyguard features that when set on a normal or organization-owned managed profile, have @@ -8977,6 +8980,10 @@ public class DevicePolicyManager { * by applications in the managed profile. * </ul> * <p> + * From version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, the profile owner of a + * managed profile can also set {@link #KEYGUARD_DISABLE_WIDGETS_ALL} which disables keyguard + * widgets for the managed profile. + * <p> * From version {@link android.os.Build.VERSION_CODES#R} the profile owner of an * organization-owned managed profile can set: * <ul> @@ -8985,6 +8992,12 @@ public class DevicePolicyManager { * <li>{@link #KEYGUARD_DISABLE_SECURE_NOTIFICATIONS} which affects the parent user when called * on the parent profile. * </ul> + * Starting from version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} the profile + * owner of an organization-owned managed profile can set: + * <ul> + * <li>{@link #KEYGUARD_DISABLE_WIDGETS_ALL} which affects the parent user when called on the + * parent profile. + * </ul> * {@link #KEYGUARD_DISABLE_TRUST_AGENTS}, {@link #KEYGUARD_DISABLE_FINGERPRINT}, * {@link #KEYGUARD_DISABLE_FACE}, {@link #KEYGUARD_DISABLE_IRIS}, * {@link #KEYGUARD_DISABLE_SECURE_CAMERA} and {@link #KEYGUARD_DISABLE_SECURE_NOTIFICATIONS} @@ -17560,6 +17573,48 @@ public class DevicePolicyManager { } /** + * Force sets the maximum storage size allowed for policies associated with an admin regardless + * of the default value set in the system, unlike {@link #setMaxPolicyStorageLimit} which can + * only set it to a value higher than the default value set by the system.Setting a limit of -1 + * effectively removes any storage restrictions. + * + * @param storageLimit Maximum storage allowed in bytes. Use -1 to disable limits. + * + * @hide + */ + @TestApi + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT) + @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED) + public void forceSetMaxPolicyStorageLimit(int storageLimit) { + if (mService != null) { + try { + mService.forceSetMaxPolicyStorageLimit(mContext.getPackageName(), storageLimit); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Retrieves the size of the current policies set by the {@code admin}. + * + * @hide + */ + @TestApi + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT) + @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED) + public int getPolicySizeForAdmin(@NonNull EnforcingAdmin admin) { + if (mService != null) { + try { + return mService.getPolicySizeForAdmin(mContext.getPackageName(), admin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return -1; + } + + /** * @return The headless device owner mode for the current set DO, returns * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set. * diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java index 7c718f6651a2..f70a53f61671 100644 --- a/core/java/android/app/admin/EnforcingAdmin.java +++ b/core/java/android/app/admin/EnforcingAdmin.java @@ -16,9 +16,13 @@ package android.app.admin; +import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED; + +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; @@ -60,6 +64,8 @@ public final class EnforcingAdmin implements Parcelable { * * @hide */ + @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED) + @TestApi public EnforcingAdmin( @NonNull String packageName, @NonNull Authority authority, @NonNull UserHandle userHandle, @Nullable ComponentName componentName) { @@ -101,6 +107,16 @@ public final class EnforcingAdmin implements Parcelable { return mUserHandle; } + /** + * Returns the {@link ComponentName} of the admin if applicable. + * + * @hide + */ + @Nullable + public ComponentName getComponentName() { + return mComponentName; + } + @Override public boolean equals(@Nullable Object o) { if (this == o) return true; diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 2002326d76bd..d1837132e1a4 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -623,8 +623,10 @@ interface IDevicePolicyManager { int[] getSubscriptionIds(String callerPackageName); - void setMaxPolicyStorageLimit(String packageName, int storageLimit); - int getMaxPolicyStorageLimit(String packageName); + void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit); + void forceSetMaxPolicyStorageLimit(String callerPackageName, int storageLimit); + int getMaxPolicyStorageLimit(String callerPackageName); + int getPolicySizeForAdmin(String callerPackageName, in EnforcingAdmin admin); int getHeadlessDeviceOwnerMode(String callerPackageName); } diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java index a36ea0508a95..68b4ad84d81a 100644 --- a/core/java/android/app/admin/LockTaskPolicy.java +++ b/core/java/android/app/admin/LockTaskPolicy.java @@ -135,7 +135,7 @@ public final class LockTaskPolicy extends PolicyValue<LockTaskPolicy> { } private void setPackagesInternal(Set<String> packages) { - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { for (String p : packages) { PolicySizeVerifier.enforceMaxPackageNameLength(p); } diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java index 389585f036db..1a04f6c908bc 100644 --- a/core/java/android/app/admin/PackagePermissionPolicyKey.java +++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java @@ -59,7 +59,7 @@ public final class PackagePermissionPolicyKey extends PolicyKey { public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName, @NonNull String permissionName) { super(identifier); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { PolicySizeVerifier.enforceMaxPackageNameLength(packageName); PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName"); } diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java index 68dc797f6513..9e31a23aec91 100644 --- a/core/java/android/app/admin/PackagePolicyKey.java +++ b/core/java/android/app/admin/PackagePolicyKey.java @@ -55,7 +55,7 @@ public final class PackagePolicyKey extends PolicyKey { @TestApi public PackagePolicyKey(@NonNull String key, @NonNull String packageName) { super(key); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { PolicySizeVerifier.enforceMaxPackageNameLength(packageName); } mPackageName = Objects.requireNonNull((packageName)); diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java index 8995c0f20de8..6efe9ad0dbed 100644 --- a/core/java/android/app/admin/StringPolicyValue.java +++ b/core/java/android/app/admin/StringPolicyValue.java @@ -30,7 +30,7 @@ public final class StringPolicyValue extends PolicyValue<String> { public StringPolicyValue(@NonNull String value) { super(value); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { PolicySizeVerifier.enforceMaxStringLength(value, "policyValue"); } } diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java index f37dfee0f9dc..12b11f4ba687 100644 --- a/core/java/android/app/admin/StringSetPolicyValue.java +++ b/core/java/android/app/admin/StringSetPolicyValue.java @@ -32,7 +32,7 @@ public final class StringSetPolicyValue extends PolicyValue<Set<String>> { public StringSetPolicyValue(@NonNull Set<String> value) { super(value); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { for (String str : value) { PolicySizeVerifier.enforceMaxStringLength(str, "policyValue"); } diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java index ee90ccd9417f..9054287cb7a0 100644 --- a/core/java/android/app/admin/UserRestrictionPolicyKey.java +++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java @@ -45,7 +45,7 @@ public final class UserRestrictionPolicyKey extends PolicyKey { @TestApi public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) { super(identifier); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction"); } mRestriction = Objects.requireNonNull(restriction); diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index fe2f7641cda6..6a07484eebc6 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -27,6 +27,17 @@ flag { } flag { + name: "device_policy_size_tracking_internal_bug_fix_enabled" + namespace: "enterprise" + description: "Bug fix for tracking the total policy size and have a max threshold" + bug: "281543351" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + +flag { name: "onboarding_bugreport_v2_enabled" is_exported: true namespace: "enterprise" @@ -232,3 +243,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "headless_single_user_bad_device_admin_state_fix" + namespace: "enterprise" + description: "Fix the bad state in DPMS caused by an earlier bug related to the headless single user change" + bug: "332477138" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 0214d40f8441..1f6ac2efd64f 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -109,4 +109,11 @@ flag { namespace: "systemui" description: "No notifs can use USAGE_UNKNOWN or USAGE_MEDIA" bug: "331793339" +} + +flag { + name: "clean_up_spans_and_new_lines" + namespace: "systemui" + description: "Cleans up spans and unnecessary new lines from standard notification templates" + bug: "313439845" }
\ No newline at end of file diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java index a8d61db1ce3a..6e7e93009993 100644 --- a/core/java/android/app/servertransaction/ClientTransactionItem.java +++ b/core/java/android/app/servertransaction/ClientTransactionItem.java @@ -53,6 +53,7 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel return true; } + // TODO(b/260873529): cleanup /** * If this {@link ClientTransactionItem} is updating configuration, returns the {@link Context} * it is updating; otherwise, returns {@code null}. diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java index c55b0f110b3b..722d5f0fe462 100644 --- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -16,6 +16,8 @@ package android.app.servertransaction; +import static android.app.WindowConfiguration.areConfigurationsEqualForDisplay; + import static com.android.window.flags.Flags.activityWindowInfoFlag; import static com.android.window.flags.Flags.bundleClientTransactionFlag; @@ -24,8 +26,11 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityThread; +import android.content.Context; +import android.content.res.Configuration; import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; +import android.util.ArrayMap; import android.util.ArraySet; import android.window.ActivityWindowInfo; @@ -51,6 +56,15 @@ public class ClientTransactionListenerController { private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>> mActivityWindowInfoChangedListeners = new ArraySet<>(); + /** + * Keeps track of the Context whose Configuration will get updated, mapping to the config before + * the change. + */ + private final ArrayMap<Context, Configuration> mContextToPreChangedConfigMap = new ArrayMap<>(); + + /** Whether there is an {@link ClientTransaction} being executed. */ + private boolean mIsClientTransactionExecuting; + /** Gets the singleton controller. */ @NonNull public static ClientTransactionListenerController getInstance() { @@ -126,18 +140,92 @@ public class ClientTransactionListenerController { } } - /** - * Called when receives a {@link ClientTransaction} that is updating display-related - * window configuration. - */ - public void onDisplayChanged(int displayId) { - if (!bundleClientTransactionFlag()) { + /** Called when starts executing a remote {@link ClientTransaction}. */ + public void onClientTransactionStarted() { + mIsClientTransactionExecuting = true; + } + + /** Called when finishes executing a remote {@link ClientTransaction}. */ + public void onClientTransactionFinished() { + notifyDisplayManagerIfNeeded(); + mIsClientTransactionExecuting = false; + } + + /** Called before updating the Configuration of the given {@code context}. */ + public void onContextConfigurationPreChanged(@NonNull Context context) { + if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) { + // Not enable for system server. + return; + } + if (mContextToPreChangedConfigMap.containsKey(context)) { + // There is an earlier change that hasn't been reported yet. return; } - if (ActivityThread.isSystem()) { + mContextToPreChangedConfigMap.put(context, + new Configuration(context.getResources().getConfiguration())); + } + + /** Called after updating the Configuration of the given {@code context}. */ + public void onContextConfigurationPostChanged(@NonNull Context context) { + if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) { // Not enable for system server. return; } + if (mIsClientTransactionExecuting) { + // Wait until #onClientTransactionFinished to prevent it from triggering the same + // #onDisplayChanged multiple times within the same ClientTransaction. + return; + } + final Configuration preChangedConfig = mContextToPreChangedConfigMap.remove(context); + if (preChangedConfig != null && shouldReportDisplayChange(context, preChangedConfig)) { + onDisplayChanged(context.getDisplayId()); + } + } + + /** + * When {@link Configuration} is changed, we want to trigger display change callback as well, + * because Display reads some fields from {@link Configuration}. + */ + private void notifyDisplayManagerIfNeeded() { + if (mContextToPreChangedConfigMap.isEmpty()) { + return; + } + // Whether the configuration change should trigger DisplayListener#onDisplayChanged. + try { + // Calculate display ids that have config changed. + final ArraySet<Integer> configUpdatedDisplayIds = new ArraySet<>(); + final int contextCount = mContextToPreChangedConfigMap.size(); + for (int i = 0; i < contextCount; i++) { + final Context context = mContextToPreChangedConfigMap.keyAt(i); + final Configuration preChangedConfig = mContextToPreChangedConfigMap.valueAt(i); + if (shouldReportDisplayChange(context, preChangedConfig)) { + configUpdatedDisplayIds.add(context.getDisplayId()); + } + } + + // Dispatch the display changed callbacks. + final int displayCount = configUpdatedDisplayIds.size(); + for (int i = 0; i < displayCount; i++) { + final int displayId = configUpdatedDisplayIds.valueAt(i); + onDisplayChanged(displayId); + } + } finally { + mContextToPreChangedConfigMap.clear(); + } + } + + private boolean shouldReportDisplayChange(@NonNull Context context, + @NonNull Configuration preChangedConfig) { + final Configuration postChangedConfig = context.getResources().getConfiguration(); + return !areConfigurationsEqualForDisplay(postChangedConfig, preChangedConfig); + } + + /** + * Called when receives a {@link Configuration} changed event that is updating display-related + * window configuration. + */ + @VisibleForTesting + public void onDisplayChanged(int displayId) { mDisplayManager.handleDisplayChangeFromWindowManager(displayId); } } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index c83719149821..480205ebc756 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -16,7 +16,6 @@ package android.app.servertransaction; -import static android.app.WindowConfiguration.areConfigurationsEqualForDisplay; import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE; import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY; import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; @@ -32,17 +31,12 @@ import static android.app.servertransaction.TransactionExecutorHelper.shouldExcl import static android.app.servertransaction.TransactionExecutorHelper.tId; import static android.app.servertransaction.TransactionExecutorHelper.transactionToString; -import static com.android.window.flags.Flags.bundleClientTransactionFlag; - import android.annotation.NonNull; import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.content.Context; -import android.content.res.Configuration; import android.os.IBinder; import android.os.Trace; -import android.util.ArrayMap; -import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; @@ -63,12 +57,6 @@ public class TransactionExecutor { private final PendingTransactionActions mPendingActions = new PendingTransactionActions(); private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper(); - /** - * Keeps track of the Context whose Configuration got updated within a transaction, mapping to - * the config before the transaction. - */ - private final ArrayMap<Context, Configuration> mContextToPreChangedConfigMap = new ArrayMap<>(); - /** Initialize an instance with transaction handler, that will execute all requested actions. */ public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) { mTransactionHandler = clientTransactionHandler; @@ -104,37 +92,6 @@ public class TransactionExecutor { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } - if (!mContextToPreChangedConfigMap.isEmpty()) { - // Whether this transaction should trigger DisplayListener#onDisplayChanged. - try { - // Calculate display ids that have config changed. - final ArraySet<Integer> configUpdatedDisplayIds = new ArraySet<>(); - final int contextCount = mContextToPreChangedConfigMap.size(); - for (int i = 0; i < contextCount; i++) { - final Context context = mContextToPreChangedConfigMap.keyAt(i); - final Configuration preTransactionConfig = - mContextToPreChangedConfigMap.valueAt(i); - final Configuration postTransactionConfig = context.getResources() - .getConfiguration(); - if (!areConfigurationsEqualForDisplay( - postTransactionConfig, preTransactionConfig)) { - configUpdatedDisplayIds.add(context.getDisplayId()); - } - } - - // Dispatch the display changed callbacks. - final ClientTransactionListenerController controller = - ClientTransactionListenerController.getInstance(); - final int displayCount = configUpdatedDisplayIds.size(); - for (int i = 0; i < displayCount; i++) { - final int displayId = configUpdatedDisplayIds.valueAt(i); - controller.onDisplayChanged(displayId); - } - } finally { - mContextToPreChangedConfigMap.clear(); - } - } - mPendingActions.clear(); if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction"); } @@ -214,20 +171,6 @@ public class TransactionExecutor { } } - final boolean shouldTrackConfigUpdatedContext = - // No configuration change for local transaction. - !mTransactionHandler.isExecutingLocalTransaction() - && bundleClientTransactionFlag(); - final Context configUpdatedContext = shouldTrackConfigUpdatedContext - ? item.getContextToUpdate(mTransactionHandler) - : null; - if (configUpdatedContext != null - && !mContextToPreChangedConfigMap.containsKey(configUpdatedContext)) { - // Keep track of the first pre-executed config of each changed Context. - mContextToPreChangedConfigMap.put(configUpdatedContext, - new Configuration(configUpdatedContext.getResources().getConfiguration())); - } - item.execute(mTransactionHandler, mPendingActions); item.postExecute(mTransactionHandler, mPendingActions); diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 451195478760..765c802c2df3 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -32,3 +32,10 @@ flag { description: "Enable support for transporting draw instructions as data parcel" bug: "286130467" } + +flag { + name: "throttle_widget_updates" + namespace: "app_widgets" + description: "Throttle the widget view updates to mitigate transaction exceptions" + bug: "326145514" +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b706cae17547..bad73fc68f3a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3256,6 +3256,14 @@ public abstract class Context { * * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. * + * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system can <a + * href="{@docRoot}develop/background-work/background-tasks/broadcasts#android-14">place + * context-registered broadcasts in a queue while the app is in the <a + * href="{@docRoot}guide/components/activities/process-lifecycle">cached state</a>. + * When the app leaves the cached state, such as returning to the + * foreground, the system delivers any queued broadcasts. Multiple instances + * of certain broadcasts might be merged into one broadcast. + * * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers * registered with this method will correctly respect the * {@link Intent#setPackage(String)} specified for an Intent being broadcast. @@ -3301,6 +3309,14 @@ public abstract class Context { * * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. * + * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system can <a + * href="{@docRoot}develop/background-work/background-tasks/broadcasts#android-14">place + * context-registered broadcasts in a queue while the app is in the <a + * href="{@docRoot}guide/components/activities/process-lifecycle">cached state</a>. + * When the app leaves the cached state, such as returning to the + * foreground, the system delivers any queued broadcasts. Multiple instances + * of certain broadcasts might be merged into one broadcast. + * * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers * registered with this method will correctly respect the * {@link Intent#setPackage(String)} specified for an Intent being broadcast. @@ -3342,6 +3358,14 @@ public abstract class Context { * * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. * + * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system can <a + * href="{@docRoot}develop/background-work/background-tasks/broadcasts#android-14">place + * context-registered broadcasts in a queue while the app is in the <a + * href="{@docRoot}guide/components/activities/process-lifecycle">cached state</a>. + * When the app leaves the cached state, such as returning to the + * foreground, the system delivers any queued broadcasts. Multiple instances + * of certain broadcasts might be merged into one broadcast. + * * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers * registered with this method will correctly respect the * {@link Intent#setPackage(String)} specified for an Intent being broadcast. @@ -3385,6 +3409,14 @@ public abstract class Context { * * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. * + * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the system can <a + * href="{@docRoot}develop/background-work/background-tasks/broadcasts#android-14">place + * context-registered broadcasts in a queue while the app is in the <a + * href="{@docRoot}guide/components/activities/process-lifecycle">cached state</a>. + * When the app leaves the cached state, such as returning to the + * foreground, the system delivers any queued broadcasts. Multiple instances + * of certain broadcasts might be merged into one broadcast. + * * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers * registered with this method will correctly respect the * {@link Intent#setPackage(String)} specified for an Intent being broadcast. diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 93fa5d85796f..eb7afb8ea82c 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -61,7 +61,9 @@ import java.util.concurrent.Executor; @SystemService(Context.CREDENTIAL_SERVICE) @RequiresFeature(PackageManager.FEATURE_CREDENTIALS) public final class CredentialManager { - private static final String TAG = "CredentialManager"; + /** @hide **/ + @Hide + public static final String TAG = "CredentialManager"; private static final Bundle OPTIONS_SENDER_BAL_OPTIN = ActivityOptions.makeBasic() .setPendingIntentBackgroundActivityStartMode( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle(); diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index dca663d206d3..50d976f683cb 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -1768,9 +1768,12 @@ public abstract class CameraDevice implements AutoCloseable { * @param sessionConfig The session configuration for which characteristics are fetched. * @return CameraCharacteristics specific to a given session configuration. * - * @throws IllegalArgumentException if the session configuration is invalid - * @throws CameraAccessException if the camera device is no longer connected or has - * encountered a fatal error + * @throws IllegalArgumentException if the session configuration is invalid or if + * {@link #isSessionConfigurationSupported} returns + * {@code false} for the provided + * {@link SessionConfiguration} + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error * * @see CameraCharacteristics#getAvailableSessionCharacteristicsKeys */ diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java index 81d0976c09bb..372839d837f9 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java @@ -104,8 +104,8 @@ public class CameraDeviceSetupImpl extends CameraDevice.CameraDeviceSetup { } try { - return cameraService.isSessionConfigurationWithParametersSupported( - mCameraId, config, mContext.getDeviceId(), + return cameraService.isSessionConfigurationWithParametersSupported(mCameraId, + mTargetSdkVersion, config, mContext.getDeviceId(), mCameraManager.getDevicePolicyFromContext(mContext)); } catch (ServiceSpecificException e) { throw ExceptionUtils.throwAsPublicException(e); diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index ec67212b46b7..89ab105e8ce3 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -364,6 +364,14 @@ public abstract class DisplayManagerInternal { public abstract List<RefreshRateLimitation> getRefreshRateLimitations(int displayId); /** + * Returns if vrr support is enabled for specified display + * + * @param displayId The id of the display. + * @return true if associated display supports dvrr + */ + public abstract boolean isVrrSupportEnabled(int displayId); + + /** * For the given displayId, updates if WindowManager is responsible for mirroring on that * display. If {@code false}, then SurfaceFlinger performs no layer mirroring to the * given display. diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java index ca1d49a93def..78fbce63b00d 100644 --- a/core/java/android/os/OomKillRecord.java +++ b/core/java/android/os/OomKillRecord.java @@ -25,7 +25,7 @@ import com.android.internal.util.FrameworkStatsLog; * Note that this class fields' should be equivalent to the struct * <b>OomKill</b> inside * <pre> - * system/memory/libmeminfo/libmemevents/include/memevents.h + * system/memory/libmeminfo/libmemevents/include/memevents/bpf_types.h * </pre> * * @hide @@ -36,14 +36,27 @@ public final class OomKillRecord { private int mUid; private String mProcessName; private short mOomScoreAdj; + private long mTotalVmInKb; + private long mAnonRssInKb; + private long mFileRssInKb; + private long mShmemRssInKb; + private long mPgTablesInKb; public OomKillRecord(long timeStampInMillis, int pid, int uid, - String processName, short oomScoreAdj) { + String processName, short oomScoreAdj, + long totalVmInKb, long anonRssInKb, + long fileRssInKb, long shmemRssInKb, + long pgTablesInKb) { this.mTimeStampInMillis = timeStampInMillis; this.mPid = pid; this.mUid = uid; this.mProcessName = processName; this.mOomScoreAdj = oomScoreAdj; + this.mTotalVmInKb = totalVmInKb; + this.mAnonRssInKb = anonRssInKb; + this.mFileRssInKb = fileRssInKb; + this.mShmemRssInKb = shmemRssInKb; + this.mPgTablesInKb = pgTablesInKb; } /** @@ -55,7 +68,8 @@ public final class OomKillRecord { FrameworkStatsLog.write( FrameworkStatsLog.KERNEL_OOM_KILL_OCCURRED, mUid, mPid, mOomScoreAdj, mTimeStampInMillis, - mProcessName); + mProcessName, mTotalVmInKb, mAnonRssInKb, + mFileRssInKb, mShmemRssInKb, mPgTablesInKb); } public long getTimestampMilli() { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index f172c3e52415..857a85d99fa6 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -705,7 +705,7 @@ public class UserManager { * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_BLUETOOTH} * can set this restriction using the DevicePolicyManager APIs mentioned below. * - * <p>Default is <code>true</code> for managed profiles and false otherwise. + * <p>Default is <code>true</code> for managed and private profiles, false otherwise. * * <p>When a device upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it * for all existing managed profiles. diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index c6d3d9b6da84..92f2c321c1db 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -65,7 +65,7 @@ import java.util.Set; * @hide */ public final class CredentialProviderInfoFactory { - private static final String TAG = "CredentialProviderInfoFactory"; + private static final String TAG = CredentialManager.TAG; private static final String TAG_CREDENTIAL_PROVIDER = "credential-provider"; private static final String TAG_CAPABILITIES = "capabilities"; diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index f4dadbb0d25a..beb4d95263be 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -17,6 +17,7 @@ package android.view; import static com.android.text.flags.Flags.handwritingCursorPosition; +import static com.android.text.flags.Flags.handwritingUnsupportedMessage; import android.annotation.FlaggedApi; import android.annotation.NonNull; @@ -607,7 +608,9 @@ public class HandwritingInitiator { final View candidateView = findBestCandidateView(hoverX, hoverY, /* isHover */ true); if (candidateView != null) { - mCachedHoverTarget = new WeakReference<>(candidateView); + if (!handwritingUnsupportedMessage()) { + mCachedHoverTarget = new WeakReference<>(candidateView); + } return candidateView; } } diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index 253073a83827..69228cafa34b 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -83,11 +83,7 @@ public class HapticFeedbackConstants { */ public static final int TEXT_HANDLE_MOVE = 9; - /** - * The user unlocked the device - * @hide - */ - public static final int ENTRY_BUMP = 10; + // REMOVED: ENTRY_BUMP = 10 /** * The user has moved the dragged object within a droppable area. @@ -230,6 +226,22 @@ public class HapticFeedbackConstants { public static final int LONG_PRESS_POWER_BUTTON = 10003; /** + * A haptic effect to signal the confirmation of a user biometric authentication + * (e.g. fingerprint reading). + * This is a private constant to be used only by system apps. + * @hide + */ + public static final int BIOMETRIC_CONFIRM = 10004; + + /** + * A haptic effect to signal the rejection of a user biometric authentication attempt + * (e.g. fingerprint reading). + * This is a private constant to be used only by system apps. + * @hide + */ + public static final int BIOMETRIC_REJECT = 10005; + + /** * Flag for {@link View#performHapticFeedback(int, int) * View.performHapticFeedback(int, int)}: Ignore the setting in the * view for whether to perform haptic feedback, do it always. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 59cb45019e83..afe5b7e15793 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1565,8 +1565,9 @@ public interface WindowManager extends ViewManager { * android:value="true|false"/> * </activity> * </pre> + * + * @hide */ - @FlaggedApi(Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING) String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING = "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING"; diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index eefc72b82c24..334965abd8c9 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -80,6 +80,16 @@ flag { } flag { + name: "migrate_enable_shortcuts" + namespace: "accessibility" + description: "Refactors deprecated code to use AccessibilityManager#enableShortcutsForTargets." + bug: "332006721" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "motion_event_observing" is_exported: true namespace: "accessibility" diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 65984f55ded2..ead8887fda35 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -45,8 +45,10 @@ import android.util.Log; import android.view.View; import android.view.WindowManager; import android.view.accessibility.IAccessibilityManager; +import android.widget.flags.Flags; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -205,27 +207,41 @@ public class Toast { INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; - tn.mNextView = new WeakReference<>(mNextView); + if (Flags.toastNoWeakref()) { + tn.mNextView = mNextView; + } else { + tn.mNextViewWeakRef = new WeakReference<>(mNextView); + } final boolean isUiContext = mContext.isUiContext(); final int displayId = mContext.getDisplayId(); + boolean wasEnqueued = false; try { if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { if (mNextView != null) { // It's a custom toast - service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext, displayId); + wasEnqueued = service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext, + displayId); } else { // It's a text toast ITransientNotificationCallback callback = new CallbackBinder(mCallbacks, mHandler); - service.enqueueTextToast(pkg, mToken, mText, mDuration, isUiContext, displayId, - callback); + wasEnqueued = service.enqueueTextToast(pkg, mToken, mText, mDuration, + isUiContext, displayId, callback); } } else { - service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext, displayId); + wasEnqueued = service.enqueueToast(pkg, mToken, tn, mDuration, isUiContext, + displayId); } } catch (RemoteException e) { // Empty + } finally { + if (Flags.toastNoWeakref()) { + if (!wasEnqueued) { + tn.mNextViewWeakRef = null; + tn.mNextView = null; + } + } } } @@ -581,6 +597,16 @@ public class Toast { } } + /** + * Get the Toast.TN ITransientNotification object + * @return TN + * @hide + */ + @VisibleForTesting + public TN getTn() { + return mTN; + } + // ======================================================================================= // All the gunk below is the interaction with the Notification Service, which handles // the proper ordering of these system-wide. @@ -599,7 +625,11 @@ public class Toast { return sService; } - private static class TN extends ITransientNotification.Stub { + /** + * @hide + */ + @VisibleForTesting + public static class TN extends ITransientNotification.Stub { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private final WindowManager.LayoutParams mParams; @@ -620,7 +650,9 @@ public class Toast { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) View mView; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - WeakReference<View> mNextView; + WeakReference<View> mNextViewWeakRef; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + View mNextView; int mDuration; WindowManager mWM; @@ -662,14 +694,22 @@ public class Toast { handleHide(); // Don't do this in handleHide() because it is also invoked by // handleShow() - mNextView = null; + if (Flags.toastNoWeakref()) { + mNextView = null; + } else { + mNextViewWeakRef = null; + } break; } case CANCEL: { handleHide(); // Don't do this in handleHide() because it is also invoked by // handleShow() - mNextView = null; + if (Flags.toastNoWeakref()) { + mNextView = null; + } else { + mNextViewWeakRef = null; + } try { getService().cancelToast(mPackageName, mToken); } catch (RemoteException e) { @@ -716,21 +756,43 @@ public class Toast { } public void handleShow(IBinder windowToken) { - if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView - + " mNextView=" + mNextView); + if (Flags.toastNoWeakref()) { + if (localLOGV) { + Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + + " mNextView=" + mNextView); + } + } else { + if (localLOGV) { + Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + + " mNextView=" + mNextViewWeakRef); + } + } // If a cancel/hide is pending - no need to show - at this point // the window token is already invalid and no need to do any work. if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) { return; } - if (mNextView != null && mView != mNextView.get()) { - // remove the old view if necessary - handleHide(); - mView = mNextView.get(); - if (mView != null) { - mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY, - mHorizontalMargin, mVerticalMargin, - new CallbackBinder(getCallbacks(), mHandler)); + if (Flags.toastNoWeakref()) { + if (mNextView != null && mView != mNextView) { + // remove the old view if necessary + handleHide(); + mView = mNextView; + if (mView != null) { + mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY, + mHorizontalMargin, mVerticalMargin, + new CallbackBinder(getCallbacks(), mHandler)); + } + } + } else { + if (mNextViewWeakRef != null && mView != mNextViewWeakRef.get()) { + // remove the old view if necessary + handleHide(); + mView = mNextViewWeakRef.get(); + if (mView != null) { + mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY, + mHorizontalMargin, mVerticalMargin, + new CallbackBinder(getCallbacks(), mHandler)); + } } } } @@ -745,6 +807,23 @@ public class Toast { mView = null; } } + + /** + * Get the next view to show for enqueued toasts + * Custom toast views are deprecated. + * @see #setView(View) + * + * @return next view + * @hide + */ + @VisibleForTesting + public View getNextView() { + if (Flags.toastNoWeakref()) { + return mNextView; + } else { + return (mNextViewWeakRef != null) ? mNextViewWeakRef.get() : null; + } + } } /** diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig index e60fa157e8e4..515fa55b7b90 100644 --- a/core/java/android/widget/flags/notification_widget_flags.aconfig +++ b/core/java/android/widget/flags/notification_widget_flags.aconfig @@ -25,4 +25,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "toast_no_weakref" + namespace: "systemui" + description: "Do not use WeakReference for custom view Toast" + bug: "321732224" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 4a3aba13fd54..a868d487b82f 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -25,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.app.ResourcesManager; +import android.app.servertransaction.ClientTransactionListenerController; import android.content.Context; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -137,12 +138,24 @@ public class WindowTokenClient extends Binder { * should be dispatched to listeners. */ @AnyThread - public void onConfigurationChanged(Configuration newConfig, int newDisplayId, + public void onConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) { final Context context = mContextRef.get(); if (context == null) { return; } + final ClientTransactionListenerController controller = + ClientTransactionListenerController.getInstance(); + controller.onContextConfigurationPreChanged(context); + try { + onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange); + } finally { + controller.onContextConfigurationPostChanged(context); + } + } + + private void onConfigurationChangedInner(@NonNull Context context, + @NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) { CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig); final boolean displayChanged; final boolean shouldUpdateResources; diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index ddb8ee0df668..e531bcbaa215 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -366,7 +366,7 @@ public class AccessibilityShortcutController { // to the Settings. final ComponentName configDefaultService = ComponentName.unflattenFromString(defaultService); - if (Flags.a11yQsShortcut()) { + if (Flags.migrateEnableShortcuts()) { am.enableShortcutsForTargets(true, HARDWARE, Set.of(configDefaultService.flattenToString()), userId); } else { @@ -384,7 +384,7 @@ public class AccessibilityShortcutController { mContext, HARDWARE, userId); - if (Flags.a11yQsShortcut()) { + if (Flags.migrateEnableShortcuts()) { am.enableShortcutsForTargets( false, HARDWARE, targetServices, userId); } else { diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java index f0885922e670..66faa318666d 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java @@ -117,7 +117,7 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS @Override public void onCheckedChanged(boolean isChecked) { setShortcutEnabled(isChecked); - if (Flags.a11yQsShortcut()) { + if (Flags.migrateEnableShortcuts()) { final AccessibilityManager am = getContext().getSystemService(AccessibilityManager.class); am.enableShortcutsForTargets( diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index eef6ce7619e8..07fa679a428a 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -229,6 +229,17 @@ public final class LongArrayMultiStateCounter implements Parcelable { } /** + * Copies time-in-state and timestamps from the supplied counter. + */ + public void copyStatesFrom(LongArrayMultiStateCounter counter) { + if (mStateCount != counter.mStateCount) { + throw new IllegalArgumentException( + "State count is not the same: " + mStateCount + " vs. " + counter.mStateCount); + } + native_copyStatesFrom(mNativeObject, counter.mNativeObject); + } + + /** * Sets the new values for the given state. */ public void setValues(int state, long[] values) { @@ -376,6 +387,10 @@ public final class LongArrayMultiStateCounter implements Parcelable { private static native void native_setState(long nativeObject, int state, long timestampMs); @CriticalNative + private static native void native_copyStatesFrom(long nativeObjectTarget, + long nativeObjectSource); + + @CriticalNative private static native void native_setValues(long nativeObject, int state, long longArrayContainerNativeObject); diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index ab982f5b67cf..9646ae957720 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -18,6 +18,7 @@ package com.android.internal.os; import android.annotation.LongDef; +import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.XmlRes; import android.compat.annotation.UnsupportedAppUsage; @@ -352,19 +353,39 @@ public class PowerProfile { * WARNING: use only for testing! */ @VisibleForTesting - public void forceInitForTesting(Context context, @XmlRes int xmlId) { + public void initForTesting(XmlPullParser parser) { + initForTesting(parser, null); + } + + /** + * Reinitialize the PowerProfile with the provided XML, using optional Resources for fallback + * configuration settings. + * WARNING: use only for testing! + */ + @VisibleForTesting + public void initForTesting(XmlPullParser parser, @Nullable Resources resources) { synchronized (sLock) { sPowerItemMap.clear(); sPowerArrayMap.clear(); sModemPowerProfile.clear(); - initLocked(context, xmlId); + + try { + readPowerValuesFromXml(parser, resources); + } finally { + if (parser instanceof XmlResourceParser) { + ((XmlResourceParser) parser).close(); + } + } + initLocked(); } } @GuardedBy("sLock") private void initLocked(Context context, @XmlRes int xmlId) { if (sPowerItemMap.size() == 0 && sPowerArrayMap.size() == 0) { - readPowerValuesFromXml(context, xmlId); + final Resources resources = context.getResources(); + XmlResourceParser parser = resources.getXml(xmlId); + readPowerValuesFromXml(parser, resources); } initLocked(); } @@ -377,9 +398,8 @@ public class PowerProfile { initModem(); } - private void readPowerValuesFromXml(Context context, @XmlRes int xmlId) { - final Resources resources = context.getResources(); - XmlResourceParser parser = resources.getXml(xmlId); + private static void readPowerValuesFromXml(XmlPullParser parser, + @Nullable Resources resources) { boolean parsingArray = false; ArrayList<Double> array = new ArrayList<>(); String arrayName = null; @@ -430,9 +450,17 @@ public class PowerProfile { } catch (IOException e) { throw new RuntimeException(e); } finally { - parser.close(); + if (parser instanceof XmlResourceParser) { + ((XmlResourceParser) parser).close(); + } } + if (resources != null) { + getDefaultValuesFromConfig(resources); + } + } + + private static void getDefaultValuesFromConfig(Resources resources) { // Now collect other config variables. int[] configResIds = new int[]{ com.android.internal.R.integer.config_bluetooth_idle_cur_ma, diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 56263fb924ea..7c7c7b8fa51d 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -47,7 +47,7 @@ public final class PowerStats { private static final BatteryStatsHistory.VarintParceler VARINT_PARCELER = new BatteryStatsHistory.VarintParceler(); - private static final byte PARCEL_FORMAT_VERSION = 1; + private static final byte PARCEL_FORMAT_VERSION = 2; private static final int PARCEL_FORMAT_VERSION_MASK = 0x000000FF; private static final int PARCEL_FORMAT_VERSION_SHIFT = @@ -57,7 +57,12 @@ public final class PowerStats { Integer.numberOfTrailingZeros(STATS_ARRAY_LENGTH_MASK); public static final int MAX_STATS_ARRAY_LENGTH = (1 << Integer.bitCount(STATS_ARRAY_LENGTH_MASK)) - 1; - private static final int UID_STATS_ARRAY_LENGTH_MASK = 0x00FF0000; + private static final int STATE_STATS_ARRAY_LENGTH_MASK = 0x00FF0000; + private static final int STATE_STATS_ARRAY_LENGTH_SHIFT = + Integer.numberOfTrailingZeros(STATE_STATS_ARRAY_LENGTH_MASK); + public static final int MAX_STATE_STATS_ARRAY_LENGTH = + (1 << Integer.bitCount(STATE_STATS_ARRAY_LENGTH_MASK)) - 1; + private static final int UID_STATS_ARRAY_LENGTH_MASK = 0xFF000000; private static final int UID_STATS_ARRAY_LENGTH_SHIFT = Integer.numberOfTrailingZeros(UID_STATS_ARRAY_LENGTH_MASK); public static final int MAX_UID_STATS_ARRAY_LENGTH = @@ -74,6 +79,10 @@ public final class PowerStats { private static final String XML_ATTR_ID = "id"; private static final String XML_ATTR_NAME = "name"; private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length"; + private static final String XML_TAG_STATE = "state"; + private static final String XML_ATTR_STATE_KEY = "key"; + private static final String XML_ATTR_STATE_LABEL = "label"; + private static final String XML_ATTR_STATE_STATS_ARRAY_LENGTH = "state-stats-array-length"; private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length"; private static final String XML_TAG_EXTRAS = "extras"; @@ -85,7 +94,24 @@ public final class PowerStats { public final int powerComponentId; public final String name; + /** + * Stats for the power component, such as the total usage time. + */ public final int statsArrayLength; + + /** + * Map of device state codes to their corresponding human-readable labels. + */ + public final SparseArray<String> stateLabels; + + /** + * Stats for a specific state of the power component, e.g. "mobile radio in the 5G mode" + */ + public final int stateStatsArrayLength; + + /** + * Stats for the usage of this power component by a specific UID (app) + */ public final int uidStatsArrayLength; /** @@ -95,17 +121,25 @@ public final class PowerStats { public final PersistableBundle extras; public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId, - int statsArrayLength, int uidStatsArrayLength, @NonNull PersistableBundle extras) { + int statsArrayLength, @Nullable SparseArray<String> stateLabels, + int stateStatsArrayLength, int uidStatsArrayLength, + @NonNull PersistableBundle extras) { this(powerComponentId, BatteryConsumer.powerComponentIdToString(powerComponentId), - statsArrayLength, uidStatsArrayLength, extras); + statsArrayLength, stateLabels, stateStatsArrayLength, uidStatsArrayLength, + extras); } public Descriptor(int customPowerComponentId, String name, int statsArrayLength, + @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength, int uidStatsArrayLength, PersistableBundle extras) { if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) { throw new IllegalArgumentException( "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH); } + if (stateStatsArrayLength > MAX_STATE_STATS_ARRAY_LENGTH) { + throw new IllegalArgumentException( + "stateStatsArrayLength is too high. Max = " + MAX_STATE_STATS_ARRAY_LENGTH); + } if (uidStatsArrayLength > MAX_UID_STATS_ARRAY_LENGTH) { throw new IllegalArgumentException( "uidStatsArrayLength is too high. Max = " + MAX_UID_STATS_ARRAY_LENGTH); @@ -113,11 +147,25 @@ public final class PowerStats { this.powerComponentId = customPowerComponentId; this.name = name; this.statsArrayLength = statsArrayLength; + this.stateLabels = stateLabels != null ? stateLabels : new SparseArray<>(); + this.stateStatsArrayLength = stateStatsArrayLength; this.uidStatsArrayLength = uidStatsArrayLength; this.extras = extras; } /** + * Returns the label associated with the give state key, e.g. "5G-high" for the + * state of Mobile Radio representing the 5G mode and high signal power. + */ + public String getStateLabel(int key) { + String label = stateLabels.get(key); + if (label != null) { + return label; + } + return name + "-" + Integer.toHexString(key); + } + + /** * Writes the Descriptor into the parcel. */ public void writeSummaryToParcel(Parcel parcel) { @@ -125,11 +173,18 @@ public final class PowerStats { & PARCEL_FORMAT_VERSION_MASK) | ((statsArrayLength << STATS_ARRAY_LENGTH_SHIFT) & STATS_ARRAY_LENGTH_MASK) + | ((stateStatsArrayLength << STATE_STATS_ARRAY_LENGTH_SHIFT) + & STATE_STATS_ARRAY_LENGTH_MASK) | ((uidStatsArrayLength << UID_STATS_ARRAY_LENGTH_SHIFT) & UID_STATS_ARRAY_LENGTH_MASK); parcel.writeInt(firstWord); parcel.writeInt(powerComponentId); parcel.writeString(name); + parcel.writeInt(stateLabels.size()); + for (int i = 0, size = stateLabels.size(); i < size; i++) { + parcel.writeInt(stateLabels.keyAt(i)); + parcel.writeString(stateLabels.valueAt(i)); + } extras.writeToParcel(parcel, 0); } @@ -148,13 +203,22 @@ public final class PowerStats { } int statsArrayLength = (firstWord & STATS_ARRAY_LENGTH_MASK) >>> STATS_ARRAY_LENGTH_SHIFT; + int stateStatsArrayLength = + (firstWord & STATE_STATS_ARRAY_LENGTH_MASK) >>> STATE_STATS_ARRAY_LENGTH_SHIFT; int uidStatsArrayLength = (firstWord & UID_STATS_ARRAY_LENGTH_MASK) >>> UID_STATS_ARRAY_LENGTH_SHIFT; int powerComponentId = parcel.readInt(); String name = parcel.readString(); + int stateLabelCount = parcel.readInt(); + SparseArray<String> stateLabels = new SparseArray<>(stateLabelCount); + for (int i = stateLabelCount; i > 0; i--) { + int key = parcel.readInt(); + String label = parcel.readString(); + stateLabels.put(key, label); + } PersistableBundle extras = parcel.readPersistableBundle(); - return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength, - extras); + return new Descriptor(powerComponentId, name, statsArrayLength, stateLabels, + stateStatsArrayLength, uidStatsArrayLength, extras); } @Override @@ -163,11 +227,13 @@ public final class PowerStats { if (!(o instanceof Descriptor)) return false; Descriptor that = (Descriptor) o; return powerComponentId == that.powerComponentId - && statsArrayLength == that.statsArrayLength - && uidStatsArrayLength == that.uidStatsArrayLength - && Objects.equals(name, that.name) - && extras.size() == that.extras.size() // Unparcel the Parcel if not yet - && Bundle.kindofEquals(extras, + && statsArrayLength == that.statsArrayLength + && stateLabels.contentEquals(that.stateLabels) + && stateStatsArrayLength == that.stateStatsArrayLength + && uidStatsArrayLength == that.uidStatsArrayLength + && Objects.equals(name, that.name) + && extras.size() == that.extras.size() // Unparcel the Parcel if not yet + && Bundle.kindofEquals(extras, that.extras); // Since the Parcel is now unparceled, do a deep comparison } @@ -179,7 +245,14 @@ public final class PowerStats { serializer.attributeInt(null, XML_ATTR_ID, powerComponentId); serializer.attribute(null, XML_ATTR_NAME, name); serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength); + serializer.attributeInt(null, XML_ATTR_STATE_STATS_ARRAY_LENGTH, stateStatsArrayLength); serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength); + for (int i = stateLabels.size() - 1; i >= 0; i--) { + serializer.startTag(null, XML_TAG_STATE); + serializer.attributeInt(null, XML_ATTR_STATE_KEY, stateLabels.keyAt(i)); + serializer.attribute(null, XML_ATTR_STATE_LABEL, stateLabels.valueAt(i)); + serializer.endTag(null, XML_TAG_STATE); + } try { serializer.startTag(null, XML_TAG_EXTRAS); extras.saveToXml(serializer); @@ -199,6 +272,8 @@ public final class PowerStats { int powerComponentId = -1; String name = null; int statsArrayLength = 0; + SparseArray<String> stateLabels = new SparseArray<>(); + int stateStatsArrayLength = 0; int uidStatsArrayLength = 0; PersistableBundle extras = null; int eventType = parser.getEventType(); @@ -212,9 +287,16 @@ public final class PowerStats { name = parser.getAttributeValue(null, XML_ATTR_NAME); statsArrayLength = parser.getAttributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH); + stateStatsArrayLength = parser.getAttributeInt(null, + XML_ATTR_STATE_STATS_ARRAY_LENGTH); uidStatsArrayLength = parser.getAttributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH); break; + case XML_TAG_STATE: + int value = parser.getAttributeInt(null, XML_ATTR_STATE_KEY); + String label = parser.getAttributeValue(null, XML_ATTR_STATE_LABEL); + stateLabels.put(value, label); + break; case XML_TAG_EXTRAS: extras = PersistableBundle.restoreFromXml(parser); break; @@ -225,11 +307,11 @@ public final class PowerStats { if (powerComponentId == -1) { return null; } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { - return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength, - extras); + return new Descriptor(powerComponentId, name, statsArrayLength, + stateLabels, stateStatsArrayLength, uidStatsArrayLength, extras); } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) { - return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength, - extras); + return new Descriptor(powerComponentId, statsArrayLength, stateLabels, + stateStatsArrayLength, uidStatsArrayLength, extras); } else { Slog.e(TAG, "Unrecognized power component: " + powerComponentId); return null; @@ -247,12 +329,14 @@ public final class PowerStats { extras.size(); // Unparcel } return "PowerStats.Descriptor{" - + "powerComponentId=" + powerComponentId - + ", name='" + name + '\'' - + ", statsArrayLength=" + statsArrayLength - + ", uidStatsArrayLength=" + uidStatsArrayLength - + ", extras=" + extras - + '}'; + + "powerComponentId=" + powerComponentId + + ", name='" + name + '\'' + + ", statsArrayLength=" + statsArrayLength + + ", stateStatsArrayLength=" + stateStatsArrayLength + + ", stateLabels=" + stateLabels + + ", uidStatsArrayLength=" + uidStatsArrayLength + + ", extras=" + extras + + '}'; } } @@ -293,6 +377,12 @@ public final class PowerStats { public long[] stats; /** + * Device-wide mode stats, used when the power component can operate in different modes, + * e.g. RATs such as LTE and 5G. + */ + public final SparseArray<long[]> stateStats = new SparseArray<>(); + + /** * Per-UID CPU stats. */ public final SparseArray<long[]> uidStats = new SparseArray<>(); @@ -313,6 +403,15 @@ public final class PowerStats { parcel.writeInt(descriptor.powerComponentId); parcel.writeLong(durationMs); VARINT_PARCELER.writeLongArray(parcel, stats); + + if (descriptor.stateStatsArrayLength != 0) { + parcel.writeInt(stateStats.size()); + for (int i = 0; i < stateStats.size(); i++) { + parcel.writeInt(stateStats.keyAt(i)); + VARINT_PARCELER.writeLongArray(parcel, stateStats.valueAt(i)); + } + } + parcel.writeInt(uidStats.size()); for (int i = 0; i < uidStats.size(); i++) { parcel.writeInt(uidStats.keyAt(i)); @@ -347,6 +446,17 @@ public final class PowerStats { stats.durationMs = parcel.readLong(); stats.stats = new long[descriptor.statsArrayLength]; VARINT_PARCELER.readLongArray(parcel, stats.stats); + + if (descriptor.stateStatsArrayLength != 0) { + int count = parcel.readInt(); + for (int i = 0; i < count; i++) { + int state = parcel.readInt(); + long[] stateStats = new long[descriptor.stateStatsArrayLength]; + VARINT_PARCELER.readLongArray(parcel, stateStats); + stats.stateStats.put(state, stateStats); + } + } + int uidCount = parcel.readInt(); for (int i = 0; i < uidCount; i++) { int uid = parcel.readInt(); @@ -376,6 +486,14 @@ public final class PowerStats { if (stats.length > 0) { sb.append("=").append(Arrays.toString(stats)); } + if (descriptor.stateStatsArrayLength != 0) { + for (int i = 0; i < stateStats.size(); i++) { + sb.append(" ["); + sb.append(descriptor.getStateLabel(stateStats.keyAt(i))); + sb.append("]="); + sb.append(Arrays.toString(stateStats.valueAt(i))); + } + } for (int i = 0; i < uidStats.size(); i++) { sb.append(uidPrefix) .append(UserHandle.formatUid(uidStats.keyAt(i))) @@ -391,6 +509,18 @@ public final class PowerStats { pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')'); pw.increaseIndent(); pw.print("duration", durationMs).println(); + if (descriptor.statsArrayLength != 0) { + pw.print("stats", Arrays.toString(stats)).println(); + } + if (descriptor.stateStatsArrayLength != 0) { + for (int i = 0; i < stateStats.size(); i++) { + pw.print("state "); + pw.print(descriptor.getStateLabel(stateStats.keyAt(i))); + pw.print(": "); + pw.print(Arrays.toString(stateStats.valueAt(i))); + pw.println(); + } + } for (int i = 0; i < uidStats.size(); i++) { pw.print("UID "); pw.print(uidStats.keyAt(i)); diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index e12becd87e12..8c7b360a3fcd 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -238,6 +238,7 @@ public class ParsingPackageUtils { */ public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; public static final int PARSE_APK_IN_APEX = 1 << 9; + public static final int PARSE_APEX = 1 << 10; public static final int PARSE_CHATTY = 1 << 31; @@ -339,6 +340,9 @@ public class ParsingPackageUtils { if ((flags & PARSE_APK_IN_APEX) != 0) { liteParseFlags |= PARSE_APK_IN_APEX; } + if ((flags & PARSE_APEX) != 0) { + liteParseFlags |= PARSE_APEX; + } final ParseResult<PackageLite> liteResult = ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags); if (liteResult.isError()) { @@ -530,7 +534,7 @@ public class ParsingPackageUtils { afterParseBaseApplication(pkg); - final ParseResult<ParsingPackage> result = validateBaseApkTags(input, pkg); + final ParseResult<ParsingPackage> result = validateBaseApkTags(input, pkg, flags); if (result.isError()) { return result; } @@ -1012,10 +1016,11 @@ public class ParsingPackageUtils { } } - return validateBaseApkTags(input, pkg); + return validateBaseApkTags(input, pkg, flags); } - private ParseResult<ParsingPackage> validateBaseApkTags(ParseInput input, ParsingPackage pkg) { + private ParseResult<ParsingPackage> validateBaseApkTags(ParseInput input, ParsingPackage pkg, + int flags) { if (!ParsedAttributionUtils.isCombinationValid(pkg.getAttributions())) { return input.error( INSTALL_PARSE_FAILED_BAD_MANIFEST, @@ -1047,6 +1052,17 @@ public class ParsingPackageUtils { adjustPackageToBeUnresizeableAndUnpipable(pkg); } + // An Apex package shouldn't have permission declarations + final boolean isApex = (flags & PARSE_APEX) != 0; + if (android.permission.flags.Flags.ignoreApexPermissions() + && isApex && !pkg.getPermissions().isEmpty()) { + return input.error( + INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + pkg.getPackageName() + + " is an APEX package and shouldn't declare permissions." + ); + } + return input.success(pkg); } diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java index b15c10e6ba20..64d3139561b6 100644 --- a/core/java/com/android/internal/power/ModemPowerProfile.java +++ b/core/java/com/android/internal/power/ModemPowerProfile.java @@ -17,14 +17,16 @@ package com.android.internal.power; import android.annotation.IntDef; -import android.content.res.XmlResourceParser; +import android.os.BatteryStats; import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseDoubleArray; +import com.android.internal.os.PowerProfile; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -95,6 +97,8 @@ public class ModemPowerProfile { */ public static final int MODEM_DRAIN_TYPE_TX = 0x3000_0000; + private static final int IGNORE = -1; + @IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = { MODEM_DRAIN_TYPE_SLEEP, MODEM_DRAIN_TYPE_IDLE, @@ -256,7 +260,7 @@ public class ModemPowerProfile { /** * Generates a ModemPowerProfile object from the <modem /> element of a power_profile.xml */ - public void parseFromXml(XmlResourceParser parser) throws IOException, + public void parseFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { final int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { @@ -286,7 +290,7 @@ public class ModemPowerProfile { } /** Parse the <active /> XML element */ - private void parseActivePowerConstantsFromXml(XmlResourceParser parser) + private void parseActivePowerConstantsFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { // Parse attributes to get the type of active modem usage the power constants are for. final int ratType; @@ -339,7 +343,7 @@ public class ModemPowerProfile { } } - private static int getTypeFromAttribute(XmlResourceParser parser, String attr, + private static int getTypeFromAttribute(XmlPullParser parser, String attr, SparseArray<String> names) { final String value = XmlUtils.readStringAttribute(parser, attr); if (value == null) { @@ -382,6 +386,84 @@ public class ModemPowerProfile { } } + public static long getAverageBatteryDrainKey(@ModemDrainType int drainType, + @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, + int txLevel) { + long key = PowerProfile.SUBSYSTEM_MODEM; + + // Attach Modem drain type to the key if specified. + if (drainType != IGNORE) { + key |= drainType; + } + + // Attach RadioAccessTechnology to the key if specified. + switch (rat) { + case IGNORE: + // do nothing + break; + case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER: + key |= MODEM_RAT_TYPE_DEFAULT; + break; + case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE: + key |= MODEM_RAT_TYPE_LTE; + break; + case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR: + key |= MODEM_RAT_TYPE_NR; + break; + default: + Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat); + } + + // Attach NR Frequency Range to the key if specified. + switch (freqRange) { + case IGNORE: + // do nothing + break; + case ServiceState.FREQUENCY_RANGE_UNKNOWN: + key |= MODEM_NR_FREQUENCY_RANGE_DEFAULT; + break; + case ServiceState.FREQUENCY_RANGE_LOW: + key |= MODEM_NR_FREQUENCY_RANGE_LOW; + break; + case ServiceState.FREQUENCY_RANGE_MID: + key |= MODEM_NR_FREQUENCY_RANGE_MID; + break; + case ServiceState.FREQUENCY_RANGE_HIGH: + key |= MODEM_NR_FREQUENCY_RANGE_HIGH; + break; + case ServiceState.FREQUENCY_RANGE_MMWAVE: + key |= MODEM_NR_FREQUENCY_RANGE_MMWAVE; + break; + default: + Log.w(TAG, "Unexpected NR frequency range : " + freqRange); + } + + // Attach transmission level to the key if specified. + switch (txLevel) { + case IGNORE: + // do nothing + break; + case 0: + key |= MODEM_TX_LEVEL_0; + break; + case 1: + key |= MODEM_TX_LEVEL_1; + break; + case 2: + key |= MODEM_TX_LEVEL_2; + break; + case 3: + key |= MODEM_TX_LEVEL_3; + break; + case 4: + key |= MODEM_TX_LEVEL_4; + break; + default: + Log.w(TAG, "Unexpected transmission level : " + txLevel); + } + return key; + } + /** * Returns the average battery drain in milli-amps of the modem for a given drain type. * Returns {@link Double.NaN} if a suitable value is not found for the given key. @@ -444,6 +526,7 @@ public class ModemPowerProfile { } return sb.toString(); } + private static void appendFieldToString(StringBuilder sb, String fieldName, SparseArray<String> names, int key) { sb.append(fieldName); diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 9c63d0dd746a..bd654fa0095c 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -1212,6 +1212,10 @@ public class ConversationLayout extends FrameLayout } } } + if (android.app.Flags.cleanUpSpansAndNewLines() && conversationText != null) { + // remove formatting from title. + conversationText = conversationText.toString(); + } if (conversationIcon == null) { conversationIcon = largeIcon; diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index f8f104943c61..97a2d3beda90 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StyleRes; +import android.app.Flags; import android.app.Person; import android.content.Context; import android.content.res.ColorStateList; @@ -200,6 +201,10 @@ public class MessagingGroup extends NotificationOptimizedLinearLayout implements if (nameOverride == null) { nameOverride = sender.getName(); } + if (Flags.cleanUpSpansAndNewLines() && nameOverride != null) { + // remove formatting from sender name + nameOverride = nameOverride.toString(); + } mSenderName = nameOverride; if (mSingleLine && !TextUtils.isEmpty(nameOverride)) { nameOverride = mContext.getResources().getString( diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index 76b05eac82af..b3c41dfe81a1 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -55,6 +55,14 @@ static void native_setState(jlong nativePtr, jint state, jlong timestamp) { counter->setState(state, timestamp); } +static void native_copyStatesFrom(jlong nativePtrTarget, jlong nativePtrSource) { + battery::LongArrayMultiStateCounter *counterTarget = + reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget); + battery::LongArrayMultiStateCounter *counterSource = + reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource); + counterTarget->copyStatesFrom(*counterSource); +} + static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) { battery::LongArrayMultiStateCounter *counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); @@ -219,6 +227,8 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = { // @CriticalNative {"native_setState", "(JIJ)V", (void *)native_setState}, // @CriticalNative + {"native_copyStatesFrom", "(JJ)V", (void *)native_copyStatesFrom}, + // @CriticalNative {"native_setValues", "(JIJ)V", (void *)native_setValues}, // @CriticalNative {"native_updateValues", "(JJJ)V", (void *)native_updateValues}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f74329903690..c694426a5aa4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2593,6 +2593,13 @@ <permission android:name="android.permission.VIBRATE_ALWAYS_ON" android:protectionLevel="signature" /> + <!-- Allows access to system-only haptic feedback constants. + <p>Protection level: signature + @hide + --> + <permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows access to the vibrator state. <p>Protection level: signature @hide @@ -3889,6 +3896,13 @@ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL" android:protectionLevel="internal|role" /> + <!-- Allows the holder to manage and retrieve max storage limit for admin policies. This + permission is only grantable on rooted devices. + @TestAPI + @hide --> + <permission android:name="android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT" + android:protectionLevel="internal" /> + <!-- Allows an application to access EnhancedConfirmationManager. @SystemApi @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") diff --git a/core/res/res/drawable/pointer_spot_anchor_vector.xml b/core/res/res/drawable/pointer_spot_anchor_vector.xml index 54de2aecb4ce..89990b80bd88 100644 --- a/core/res/res/drawable/pointer_spot_anchor_vector.xml +++ b/core/res/res/drawable/pointer_spot_anchor_vector.xml @@ -22,4 +22,8 @@ <path android:fillColor="#ADC6E7" android:pathData="M12 3c-4.963 0-9 4.038-9 9 0 4.963 4.037 9 9 9s9-4.037 9-9c0-4.962-4.037-9-9-9m0 17c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" /> <path android:fillColor="#ADC6E7" android:pathData="M12 5c-3.859 0-7 3.14-7 7s3.141 7 7 7 7-3.141 7-7-3.141-7-7-7m0 13c-3.309 0-6-2.691-6-6s2.691-6 6-6 6 2.691 6 6-2.691 6-6 6" /> </group> + <path + android:pathData="M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0 -16m0 15a7 7 0 1 1 0 -14 7 7 0 0 1 0 14" + android:fillColor="#99FFFFFF" + android:fillType="evenOdd"/> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_spot_hover_vector.xml b/core/res/res/drawable/pointer_spot_hover_vector.xml index ef596c470480..4bf5fbced36d 100644 --- a/core/res/res/drawable/pointer_spot_hover_vector.xml +++ b/core/res/res/drawable/pointer_spot_hover_vector.xml @@ -22,4 +22,7 @@ <path android:fillColor="#ADC6E7" android:pathData="M12 3c-4.963 0-9 4.038-9 9 0 4.963 4.037 9 9 9s9-4.037 9-9c0-4.962-4.037-9-9-9m0 17c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" /> <path android:fillColor="#ADC6E7" android:pathData="M12 7c-2.757 0-5 2.243-5 5s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5m0 9c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4" /> </group> + <path + android:pathData="M12 3.998a8.002 8.002 0 1 0 0 16.004 8.002 8.002 0 0 0 0 -16.004m0 13.004a5.002 5.002 0 1 1 0 -10.004 5.002 5.002 0 0 1 0 10.004" + android:fillColor="#99FFFFFF"/> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_spot_touch_vector.xml b/core/res/res/drawable/pointer_spot_touch_vector.xml index afd2956858fa..a25ffe0da5bd 100644 --- a/core/res/res/drawable/pointer_spot_touch_vector.xml +++ b/core/res/res/drawable/pointer_spot_touch_vector.xml @@ -21,4 +21,7 @@ <path android:fillColor="#ADC6E7" android:pathData="M21 12c0-4.963-4.038-9-9-9s-9 4.037-9 9 4.038 9 9 9 9-4.037 9-9m-9 8c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" /> + <path + android:pathData="M12 12m-8 0a8 8 0 1 1 16 0a8 8 0 1 1 -16 0" + android:fillColor="#99FFFFFF"/> </vector>
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d4db244a852b..2115f64a60b3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4334,9 +4334,6 @@ --> <bool name="config_wallpaperTopApp">false</bool> - <!-- True if the device supports dVRR --> - <bool name="config_supportsDvrr">false</bool> - <!-- True if the device supports at least one form of multi-window. E.g. freeform, split-screen, picture-in-picture. --> <bool name="config_supportsMultiWindow">true</bool> diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml index e42962ce9195..ae4789937832 100644 --- a/core/res/res/values/config_battery_stats.xml +++ b/core/res/res/values/config_battery_stats.xml @@ -32,6 +32,9 @@ devices--> <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer> + <!-- Mobile Radio power stats collection throttle period in milliseconds. --> + <integer name="config_defaultPowerStatsThrottlePeriodMobileRadio">3600000</integer> + <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power stats aggregation procedure is performed and the results stored in PowerStatsStore. --> <integer name="config_powerStatsAggregationPeriod">14400000</integer> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f33e2771879d..9e0954093eb2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -404,7 +404,6 @@ <java-symbol type="bool" name="config_supportAudioSourceUnprocessed" /> <java-symbol type="bool" name="config_freeformWindowManagement" /> <java-symbol type="bool" name="config_supportsBubble" /> - <java-symbol type="bool" name="config_supportsDvrr" /> <java-symbol type="bool" name="config_supportsMultiWindow" /> <java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" /> <java-symbol type="bool" name="config_supportsMultiDisplay" /> @@ -5217,6 +5216,7 @@ <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" /> <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" /> <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" /> + <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodMobileRadio" /> <java-symbol type="integer" name="config_powerStatsAggregationPeriod" /> <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index c8625b9114da..9bb72d9db6c7 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -28,7 +28,7 @@ standard SMS rate. The user is warned when the destination phone number matches the "pattern" or "premium" regexes, and does not match the "free" or "standard" regexes. --> - <!-- Harmonised European Short Codes are 6 digit numbers starting with 116 (free helplines). + <!-- Harmonised European Short Codes are 7 digit numbers starting with 116 (free helplines). Premium patterns include short codes from: http://aonebill.com/coverage&tariffs and http://mobilcent.com/info-worldwide.asp and extracted from: http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ --> @@ -39,8 +39,8 @@ <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> - <!-- Argentina: 5 digits, known short codes listed --> - <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077|78887" /> + <!-- Argentina: 6 digits, known short codes listed --> + <shortcode country="ar" pattern="\\d{1,6}" free="11711|28291|44077|78887|191289|39010" /> <!-- Armenia: 3-5 digits, emergency numbers 10[123] --> <shortcode country="am" pattern="\\d{3,5}" premium="11[2456]1|3024" free="10[123]|71522|71512|71502" /> @@ -67,7 +67,7 @@ <shortcode country="bh" pattern="\\d{1,5}" free="81181|85999" /> <!-- Brazil: 1-5 digits (standard system default, not country specific) --> - <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d|876|5500|9963|4141|8000" /> + <shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d|876|5500|9963|4141|8000|2652" /> <!-- Belarus: 4 digits --> <shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" /> @@ -163,7 +163,7 @@ <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" /> <!-- Indonesia: 1-5 digits (standard system default, not country specific) --> - <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" /> + <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457|99265" /> <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU: http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf --> @@ -172,6 +172,9 @@ <!-- Israel: 1-5 digits, known premium codes listed --> <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" /> + <!-- Iran: 4-6 digits, known premium codes listed --> + <shortcode country="ir" pattern="\\d{4,6}" free="700791|700792" /> + <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU: https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf --> <shortcode country="it" pattern="\\d{5}" premium="44[0-4]\\d{2}|47[0-4]\\d{2}|48[0-4]\\d{2}|44[5-9]\\d{4}|47[5-9]\\d{4}|48[5-9]\\d{4}|455\\d{2}|499\\d{2}" free="116\\d{3}|4112503|40\\d{0,12}" standard="430\\d{2}|431\\d{2}|434\\d{4}|435\\d{4}|439\\d{7}" /> @@ -219,11 +222,11 @@ <!-- Mozambique: 1-5 digits (standard system default, not country specific) --> <shortcode country="mz" pattern="\\d{1,5}" free="1714" /> - <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" /> + <!-- Mexico: 4-7 digits (not confirmed), known premium codes listed --> + <shortcode country="mx" pattern="\\d{4,7}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346|3030303" /> <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> - <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" /> + <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668|66966" /> <!-- Namibia: 1-5 digits (standard system default, not country specific) --> <shortcode country="na" pattern="\\d{1,5}" free="40005" /> @@ -255,6 +258,9 @@ <!-- Palestine: 5 digits, known premium codes listed --> <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" /> + <!-- Paraguay: 6 digits, known premium codes listed --> + <shortcode country="py" pattern="\\d{6}" free="191289" /> + <!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU --> <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" /> @@ -275,7 +281,7 @@ <shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)|8424" free="6954|8501" standard="2037|2044"/> <!-- Rwanda: 4 digits --> - <shortcode country="rw" pattern="\\d{4}" free="5060" /> + <shortcode country="rw" pattern="\\d{4}" free="5060|5061" /> <!-- Saudi Arabia --> <shortcode country="sa" pattern="\\d{1,5}" free="8145" /> @@ -309,7 +315,10 @@ <shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" /> <!-- Tanzania: 1-5 digits (standard system default, not country specific) --> - <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234" /> + <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234|15324" /> + + <!-- Tunisia: 5 digits, known premium codes listed --> + <shortcode country="tn" pattern="\\d{5}" free="85799" /> <!-- Turkey --> <shortcode country="tr" pattern="\\d{1,5}" free="7529|5528|6493|3193" /> @@ -324,8 +333,11 @@ visual voicemail code for T-Mobile: 122 --> <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831" /> - <!--Uruguay : 1-5 digits (standard system default, not country specific) --> - <shortcode country="uy" pattern="\\d{1,5}" free="55002" /> + <!--Uruguay : 1-6 digits (standard system default, not country specific) --> + <shortcode country="uy" pattern="\\d{1,6}" free="55002|191289" /> + + <!-- Venezuela: 1-6 digits (standard system default, not country specific) --> + <shortcode country="ve" pattern="\\d{1,6}" free="538352" /> <!-- Vietnam: 1-5 digits (standard system default, not country specific) --> <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055|8079" /> @@ -336,6 +348,9 @@ <!-- South Africa --> <shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056|33009" /> + <!-- Yemen --> + <shortcode country="ye" pattern="\\d{1,4}" free="5081" /> + <!-- Zimbabwe --> <shortcode country="zw" pattern="\\d{1,5}" free="33679" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 48082048691c..404e8731d5c5 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -267,5 +267,10 @@ android_ravenwood_test { generate_get_transaction_name: true, local_include_dirs: ["aidl"], }, + java_resources: [ + "res/xml/power_profile_test.xml", + "res/xml/power_profile_test_cpu_legacy.xml", + "res/xml/power_profile_test_modem.xml", + ], auto_gen_config: true, } diff --git a/core/tests/coretests/res/xml/power_profile_test.xml b/core/tests/coretests/res/xml/power_profile_test.xml index 322ae05bc63e..7356c9e38012 100644 --- a/core/tests/coretests/res/xml/power_profile_test.xml +++ b/core/tests/coretests/res/xml/power_profile_test.xml @@ -98,4 +98,16 @@ <value>40</value> <value>50</value> </array> -</device>
\ No newline at end of file + + <!-- Idle current for bluetooth in mA.--> + <item name="bluetooth.controller.idle">0.02</item> + + <!-- Rx current for bluetooth in mA.--> + <item name="bluetooth.controller.rx">3</item> + + <!-- Tx current for bluetooth in mA--> + <item name="bluetooth.controller.tx">5</item> + + <!-- Operating voltage for bluetooth in mV.--> + <item name="bluetooth.controller.voltage">3300</item> +</device> diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index 77d31a5f27e7..8506905e6ca0 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -23,11 +23,17 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.app.Activity; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; @@ -76,6 +82,13 @@ public class ClientTransactionListenerControllerTest { private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener; @Mock private IBinder mActivityToken; + @Mock + private Activity mActivity; + @Mock + private Resources mResources; + + private Configuration mConfiguration; + private DisplayManagerGlobal mDisplayManager; private Handler mHandler; @@ -88,7 +101,12 @@ public class ClientTransactionListenerControllerTest { MockitoAnnotations.initMocks(this); mDisplayManager = new DisplayManagerGlobal(mIDisplayManager); mHandler = getInstrumentation().getContext().getMainThreadHandler(); - mController = ClientTransactionListenerController.createInstanceForTesting(mDisplayManager); + mController = spy(ClientTransactionListenerController + .createInstanceForTesting(mDisplayManager)); + + mConfiguration = new Configuration(); + doReturn(mConfiguration).when(mResources).getConfiguration(); + doReturn(mResources).when(mActivity).getResources(); } @Test @@ -107,6 +125,43 @@ public class ClientTransactionListenerControllerTest { } @Test + public void testOnContextConfigurationChanged() { + doNothing().when(mController).onDisplayChanged(anyInt()); + doReturn(123).when(mActivity).getDisplayId(); + + // Not trigger onDisplayChanged when there is no change. + mController.onContextConfigurationPreChanged(mActivity); + mController.onContextConfigurationPostChanged(mActivity); + + verify(mController, never()).onDisplayChanged(anyInt()); + + mController.onContextConfigurationPreChanged(mActivity); + mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200)); + mController.onContextConfigurationPostChanged(mActivity); + + verify(mController).onDisplayChanged(123); + } + + @Test + public void testOnContextConfigurationChanged_duringClientTransaction() { + doNothing().when(mController).onDisplayChanged(anyInt()); + doReturn(123).when(mActivity).getDisplayId(); + + // Not trigger onDisplayChanged until ClientTransaction finished execution. + mController.onClientTransactionStarted(); + + mController.onContextConfigurationPreChanged(mActivity); + mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200)); + mController.onContextConfigurationPostChanged(mActivity); + + verify(mController, never()).onDisplayChanged(anyInt()); + + mController.onClientTransactionFinished(); + + verify(mController).onDisplayChanged(123); + } + + @Test public void testActivityWindowInfoChangedListener() { mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index 5fab1a0f3c8e..d560ef243a06 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -420,7 +420,7 @@ public class AccessibilityShortcutControllerTest { } @Test - @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); @@ -443,7 +443,7 @@ public class AccessibilityShortcutControllerTest { } @Test - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void testClickingDisableButtonInDialog_shouldClearShortcutId_old() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); configureValidShortcutService(); @@ -467,7 +467,7 @@ public class AccessibilityShortcutControllerTest { @Test @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE) - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService() throws Exception { configureApplicationTargetSdkVersion(Build.VERSION_CODES.R); @@ -480,7 +480,7 @@ public class AccessibilityShortcutControllerTest { @Test @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE) - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService() throws Exception { configureApplicationTargetSdkVersion(Build.VERSION_CODES.R); @@ -527,7 +527,7 @@ public class AccessibilityShortcutControllerTest { } @Test - @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); @@ -551,7 +551,7 @@ public class AccessibilityShortcutControllerTest { } @Test - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn_old() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); @@ -574,7 +574,7 @@ public class AccessibilityShortcutControllerTest { } @Test - @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); @@ -598,7 +598,7 @@ public class AccessibilityShortcutControllerTest { } @Test - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff_old() throws Exception { configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN); diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java index a14d8e034b32..9cac3120d1d9 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java @@ -117,7 +117,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest { } @Test - @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void onCheckedChanged_true_callA11yManagerToUpdateShortcuts() throws Exception { mSut.onCheckedChanged(true); @@ -130,7 +130,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest { } @Test - @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @EnableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void onCheckedChanged_false_callA11yManagerToUpdateShortcuts() throws Exception { mSut.onCheckedChanged(false); verify(mAccessibilityManagerService).enableShortcutsForTargets( @@ -142,7 +142,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest { } @Test - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void onCheckedChanged_turnOnShortcut_hasOtherShortcut_serviceKeepsOn() { enableA11yService(/* enable= */ true); addShortcutForA11yService( @@ -155,7 +155,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest { } @Test - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void onCheckedChanged_turnOnShortcut_noOtherShortcut_shouldTurnOnService() { enableA11yService(/* enable= */ false); addShortcutForA11yService( @@ -168,7 +168,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest { } @Test - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void onCheckedChanged_turnOffShortcut_hasOtherShortcut_serviceKeepsOn() { enableA11yService(/* enable= */ true); addShortcutForA11yService( @@ -181,7 +181,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest { } @Test - @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT) + @DisableFlags(Flags.FLAG_MIGRATE_ENABLE_SHORTCUTS) public void onCheckedChanged_turnOffShortcut_noOtherShortcut_shouldTurnOffService() { enableA11yService(/* enable= */ true); addShortcutForA11yService( 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 533b799341c1..fa5d72a04b88 100644 --- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java @@ -55,6 +55,21 @@ public class LongArrayMultiStateCounterTest { } @Test + public void copyStatesFrom() { + LongArrayMultiStateCounter source = new LongArrayMultiStateCounter(2, 1); + updateValue(source, new long[]{0}, 1000); + source.setState(0, 1000); + source.setState(1, 2000); + + LongArrayMultiStateCounter target = new LongArrayMultiStateCounter(2, 1); + target.copyStatesFrom(source); + updateValue(target, new long[]{1000}, 5000); + + assertCounts(target, 0, new long[]{250}); + assertCounts(target, 1, new long[]{750}); + } + + @Test public void setValue() { LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4); 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 c0f0714e52cc..951fa98caf27 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -21,20 +21,21 @@ import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -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.annotations.DisabledOnRavenwood; import android.platform.test.ravenwood.RavenwoodRule; +import android.util.Xml; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.frameworks.coretests.R; import com.android.internal.power.ModemPowerProfile; import com.android.internal.util.XmlUtils; @@ -43,6 +44,11 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.StringReader; /* * Keep this file in sync with frameworks/base/core/res/res/xml/power_profile_test.xml and @@ -53,7 +59,6 @@ import org.junit.runner.RunWith; */ @SmallTest @RunWith(AndroidJUnit4.class) -@IgnoreUnderRavenwood(blockedBy = PowerProfile.class) public class PowerProfileTest { @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); @@ -62,17 +67,15 @@ public class PowerProfileTest { static final String ATTR_NAME = "name"; private PowerProfile mProfile; - private Context mContext; @Before public void setUp() { - mContext = InstrumentationRegistry.getContext(); - mProfile = new PowerProfile(mContext); + mProfile = new PowerProfile(); } @Test public void testPowerProfile() { - mProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mProfile.initForTesting(resolveParser("power_profile_test")); assertEquals(5.0, mProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND)); assertEquals(1.11, mProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)); @@ -127,11 +130,36 @@ public class PowerProfileTest { PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); + + assertEquals(0.02, mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE)); + assertEquals(3, mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX)); + assertEquals(5, mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX)); + assertEquals(3300, mProfile.getAveragePower( + PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)); + } + + @DisabledOnRavenwood + @Test + public void configDefaults() throws XmlPullParserException { + Resources mockResources = mock(Resources.class); + when(mockResources.getInteger(com.android.internal.R.integer.config_bluetooth_rx_cur_ma)) + .thenReturn(123); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new StringReader( + "<device name='Android'>" + + "<item name='bluetooth.controller.idle'>10</item>" + + "</device>")); + mProfile.initForTesting(parser, mockResources); + assertThat(mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE)) + .isEqualTo(10); + assertThat(mProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX)) + .isEqualTo(123); } + @Test public void testPowerProfile_legacyCpuConfig() { // This power profile has per-cluster data, rather than per-policy - mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_cpu_legacy); + mProfile.initForTesting(resolveParser("power_profile_test_cpu_legacy")); assertEquals(2.11, mProfile.getAveragePowerForCpuScalingPolicy(0)); assertEquals(2.22, mProfile.getAveragePowerForCpuScalingPolicy(4)); @@ -148,7 +176,7 @@ public class PowerProfileTest { @Test public void testModemPowerProfile_defaultRat() throws Exception { - final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + final XmlPullParser parser = getTestModemElement("power_profile_test_modem", "testModemPowerProfile_defaultRat"); ModemPowerProfile mpp = new ModemPowerProfile(); mpp.parseFromXml(parser); @@ -216,7 +244,7 @@ public class PowerProfileTest { @Test public void testModemPowerProfile_partiallyDefined() throws Exception { - final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + final XmlPullParser parser = getTestModemElement("power_profile_test_modem", "testModemPowerProfile_partiallyDefined"); ModemPowerProfile mpp = new ModemPowerProfile(); mpp.parseFromXml(parser); @@ -369,7 +397,7 @@ public class PowerProfileTest { @Test public void testModemPowerProfile_fullyDefined() throws Exception { - final XmlResourceParser parser = getTestModemElement(R.xml.power_profile_test_modem, + final XmlPullParser parser = getTestModemElement("power_profile_test_modem", "testModemPowerProfile_fullyDefined"); ModemPowerProfile mpp = new ModemPowerProfile(); mpp.parseFromXml(parser); @@ -519,11 +547,10 @@ public class PowerProfileTest { | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); } - private XmlResourceParser getTestModemElement(@XmlRes int xmlId, String elementName) + private XmlPullParser getTestModemElement(String resourceName, String elementName) throws Exception { + XmlPullParser parser = resolveParser(resourceName); final String element = TAG_TEST_MODEM; - final Resources resources = mContext.getResources(); - XmlResourceParser parser = resources.getXml(xmlId); while (true) { XmlUtils.nextElement(parser); final String e = parser.getName(); @@ -535,10 +562,26 @@ public class PowerProfileTest { return parser; } - fail("Unanable to find element " + element + " with name " + elementName); + fail("Unable to find element " + element + " with name " + elementName); return null; } + private XmlPullParser resolveParser(String resourceName) { + if (RavenwoodRule.isOnRavenwood()) { + try { + return Xml.resolvePullParser(getClass().getClassLoader() + .getResourceAsStream("res/xml/" + resourceName + ".xml")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + Context context = androidx.test.InstrumentationRegistry.getContext(); + Resources resources = context.getResources(); + int resId = resources.getIdentifier(resourceName, "xml", context.getPackageName()); + return resources.getXml(resId); + } + } + private void assertEquals(double expected, double actual) { Assert.assertEquals(expected, actual, 0.1); } 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 b99e2026ef26..6402206410b5 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java @@ -21,20 +21,27 @@ 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 android.util.SparseArray; +import android.util.Xml; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; + @RunWith(AndroidJUnit4.class) @SmallTest -@IgnoreUnderRavenwood(reason = "Needs kernel support") public class PowerStatsTest { @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); @@ -47,7 +54,10 @@ public class PowerStatsTest { mRegistry = new PowerStats.DescriptorRegistry(); PersistableBundle extras = new PersistableBundle(); extras.putBoolean("hasPowerMonitor", true); - mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, 2, extras); + SparseArray<String> stateLabels = new SparseArray<>(); + stateLabels.put(0x0F, "idle"); + mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, stateLabels, + 1, 2, extras); mRegistry.register(mDescriptor); } @@ -58,6 +68,8 @@ public class PowerStatsTest { stats.stats[0] = 10; stats.stats[1] = 20; stats.stats[2] = 30; + stats.stateStats.put(0x0F, new long[]{16}); + stats.stateStats.put(0xF0, new long[]{17}); stats.uidStats.put(42, new long[]{40, 50}); stats.uidStats.put(99, new long[]{60, 70}); @@ -73,6 +85,7 @@ public class PowerStatsTest { assertThat(newDescriptor.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU); assertThat(newDescriptor.name).isEqualTo("cpu"); assertThat(newDescriptor.statsArrayLength).isEqualTo(3); + assertThat(newDescriptor.stateStatsArrayLength).isEqualTo(1); assertThat(newDescriptor.uidStatsArrayLength).isEqualTo(2); assertThat(newDescriptor.extras.getBoolean("hasPowerMonitor")).isTrue(); @@ -81,6 +94,11 @@ public class PowerStatsTest { PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry); assertThat(newStats.durationMs).isEqualTo(1234); assertThat(newStats.stats).isEqualTo(new long[]{10, 20, 30}); + assertThat(newStats.stateStats.size()).isEqualTo(2); + assertThat(newStats.stateStats.get(0x0F)).isEqualTo(new long[]{16}); + assertThat(newStats.descriptor.getStateLabel(0x0F)).isEqualTo("idle"); + assertThat(newStats.stateStats.get(0xF0)).isEqualTo(new long[]{17}); + assertThat(newStats.descriptor.getStateLabel(0xF0)).isEqualTo("cpu-f0"); assertThat(newStats.uidStats.size()).isEqualTo(2); assertThat(newStats.uidStats.get(42)).isEqualTo(new long[]{40, 50}); assertThat(newStats.uidStats.get(99)).isEqualTo(new long[]{60, 70}); @@ -90,9 +108,33 @@ public class PowerStatsTest { } @Test + public void xmlFormat() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + mDescriptor.writeXml(serializer); + serializer.flush(); + + byte[] bytes = out.toByteArray(); + + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + parser.setInput(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8.name()); + PowerStats.Descriptor actual = PowerStats.Descriptor.createFromXml(parser); + + assertThat(actual.powerComponentId).isEqualTo(BatteryConsumer.POWER_COMPONENT_CPU); + assertThat(actual.name).isEqualTo("cpu"); + assertThat(actual.statsArrayLength).isEqualTo(3); + assertThat(actual.stateStatsArrayLength).isEqualTo(1); + assertThat(actual.getStateLabel(0x0F)).isEqualTo("idle"); + assertThat(actual.getStateLabel(0xF0)).isEqualTo("cpu-f0"); + assertThat(actual.uidStatsArrayLength).isEqualTo(2); + assertThat(actual.extras.getBoolean("hasPowerMonitor")).isEqualTo(true); + } + + @Test public void parceling_unrecognizedPowerComponent() { PowerStats stats = new PowerStats( - new PowerStats.Descriptor(777, "luck", 3, 2, new PersistableBundle())); + new PowerStats.Descriptor(777, "luck", 3, null, 1, 2, new PersistableBundle())); stats.durationMs = 1234; Parcel parcel = Parcel.obtain(); diff --git a/core/tests/mockingcoretests/Android.bp b/core/tests/mockingcoretests/Android.bp index 2d778b1218d2..aca52a870655 100644 --- a/core/tests/mockingcoretests/Android.bp +++ b/core/tests/mockingcoretests/Android.bp @@ -40,6 +40,7 @@ android_test { "platform-test-annotations", "truth", "testables", + "flag-junit", ], libs: [ diff --git a/core/tests/mockingcoretests/src/android/widget/OWNERS b/core/tests/mockingcoretests/src/android/widget/OWNERS new file mode 100644 index 000000000000..c0cbea98cc57 --- /dev/null +++ b/core/tests/mockingcoretests/src/android/widget/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/notification/OWNERS
\ No newline at end of file diff --git a/core/tests/mockingcoretests/src/android/widget/ToastTest.java b/core/tests/mockingcoretests/src/android/widget/ToastTest.java new file mode 100644 index 000000000000..79bc81d57727 --- /dev/null +++ b/core/tests/mockingcoretests/src/android/widget/ToastTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +import android.app.INotificationManager; +import android.content.Context; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.view.View; +import android.widget.flags.Flags; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + + +/** + * ToastTest tests {@link Toast}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ToastTest { + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private Context mContext; + private MockitoSession mMockingSession; + private static INotificationManager.Stub sMockNMS; + + @Before + public void setup() { + mContext = InstrumentationRegistry.getContext(); + mMockingSession = + ExtendedMockito.mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(ServiceManager.class) + .startMocking(); + + //Toast caches the NotificationManager service as static class member + if (sMockNMS == null) { + sMockNMS = mock(INotificationManager.Stub.class); + } + doReturn(sMockNMS).when(sMockNMS).queryLocalInterface("android.app.INotificationManager"); + doReturn(sMockNMS).when(() -> ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + reset(sMockNMS); + } + + @Test + @EnableFlags(Flags.FLAG_TOAST_NO_WEAKREF) + public void enqueueFail_nullifiesNextView() throws RemoteException { + Looper.prepare(); + + // allow 1st toast and fail on the 2nd + when(sMockNMS.enqueueToast(anyString(), any(), any(), anyInt(), anyBoolean(), + anyInt())).thenReturn(true, false); + + // first toast is enqueued + Toast t = Toast.makeText(mContext, "Toast1", Toast.LENGTH_SHORT); + t.setView(mock(View.class)); + t.show(); + Toast.TN tn = t.getTn(); + assertThat(tn.getNextView()).isNotNull(); + + // second toast is not enqueued + t = Toast.makeText(mContext, "Toast2", Toast.LENGTH_SHORT); + t.setView(mock(View.class)); + t.show(); + tn = t.getTn(); + assertThat(tn.getNextView()).isNull(); + } + + @Test + @DisableFlags(Flags.FLAG_TOAST_NO_WEAKREF) + public void enqueueFail_doesNotNullifyNextView() throws RemoteException { + Looper.prepare(); + + // allow 1st toast and fail on the 2nd + when(sMockNMS.enqueueToast(anyString(), any(), any(), anyInt(), anyBoolean(), + anyInt())).thenReturn(true, false); + + // first toast is enqueued + Toast t = Toast.makeText(mContext, "Toast1", Toast.LENGTH_SHORT); + t.setView(mock(View.class)); + t.show(); + Toast.TN tn = t.getTn(); + assertThat(tn.getNextView()).isNotNull(); + + // second toast is not enqueued + t = Toast.makeText(mContext, "Toast2", Toast.LENGTH_SHORT); + t.setView(mock(View.class)); + t.show(); + tn = t.getTn(); + assertThat(tn.getNextView()).isNotNull(); + } +} diff --git a/core/tests/overlaytests/device_non_system/Android.bp b/core/tests/overlaytests/device_non_system/Android.bp new file mode 100644 index 000000000000..dd7786a4e43f --- /dev/null +++ b/core/tests/overlaytests/device_non_system/Android.bp @@ -0,0 +1,39 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "OverlayDeviceTestsNonSystem", + team: "trendy_team_android_resources", + srcs: ["src/**/*.java"], + platform_apis: true, + certificate: "platform", + static_libs: [ + "androidx.test.rules", + "testng", + "compatibility-device-util-axt", + ], + test_suites: ["device-tests"], + data: [ + ":OverlayDeviceTestsNonSystem_AppOverlay", + ], +} diff --git a/core/tests/overlaytests/device_non_system/AndroidManifest.xml b/core/tests/overlaytests/device_non_system/AndroidManifest.xml new file mode 100644 index 000000000000..a37d1689e80e --- /dev/null +++ b/core/tests/overlaytests/device_non_system/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.non_system"> + + <uses-sdk android:minSdkVersion="34" /> + + <application> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.overlaytest.non_system" + android:label="Runtime resource overlay tests for non system app" /> +</manifest> diff --git a/core/tests/overlaytests/device_non_system/AndroidTest.xml b/core/tests/overlaytests/device_non_system/AndroidTest.xml new file mode 100644 index 000000000000..fc47e6a90880 --- /dev/null +++ b/core/tests/overlaytests/device_non_system/AndroidTest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<configuration description="Test module config for OverlayDeviceTestsNonSystem"> + <option name="test-tag" value="OverlayDeviceTestsNonSystem" /> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="OverlayDeviceTestsNonSystem_AppOverlay.apk" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer"> + <option name="test-package-name" value="com.android.overlaytest.non_system" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="test-user-token" value="%TEST_USER%"/> + <option name="run-command" + value="cmd overlay enable --user %TEST_USER% com.android.overlaytest.non_system.app_overlay" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="OverlayDeviceTestsNonSystem.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.overlaytest.non_system" /> + </test> +</configuration> diff --git a/core/tests/overlaytests/device_non_system/res/layout/layout.xml b/core/tests/overlaytests/device_non_system/res/layout/layout.xml new file mode 100644 index 000000000000..2cb201377d52 --- /dev/null +++ b/core/tests/overlaytests/device_non_system/res/layout/layout.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/text_view_id" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:text="@string/test_string" /> +</LinearLayout>
\ No newline at end of file diff --git a/core/tests/overlaytests/device_non_system/res/values/overlayable.xml b/core/tests/overlaytests/device_non_system/res/values/overlayable.xml new file mode 100644 index 000000000000..f8017bc35d5d --- /dev/null +++ b/core/tests/overlaytests/device_non_system/res/values/overlayable.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <overlayable name="TestResources"> + <policy type="public"> + <item type="string" name="test_string" /> + </policy> + </overlayable> +</resources>
\ No newline at end of file diff --git a/core/tests/overlaytests/device_non_system/res/values/strings.xml b/core/tests/overlaytests/device_non_system/res/values/strings.xml new file mode 100644 index 000000000000..ff501a0639ff --- /dev/null +++ b/core/tests/overlaytests/device_non_system/res/values/strings.xml @@ -0,0 +1,3 @@ +<resources> + <string name="test_string">Original</string> +</resources>
\ No newline at end of file diff --git a/core/tests/overlaytests/device_non_system/src/com/android/overlaytest/OverlayTest.java b/core/tests/overlaytests/device_non_system/src/com/android/overlaytest/OverlayTest.java new file mode 100644 index 000000000000..2b0fe6cdc559 --- /dev/null +++ b/core/tests/overlaytests/device_non_system/src/com/android/overlaytest/OverlayTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.overlaytest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import androidx.test.InstrumentationRegistry; + +import com.android.overlaytest.non_system.R; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * This test class is to verify overlay behavior for non-system apps. + */ +@RunWith(JUnit4.class) +public class OverlayTest { + @Test + public void testStringOverlay() throws Throwable { + final LayoutInflater inflater = LayoutInflater.from(InstrumentationRegistry.getContext()); + final View layout = inflater.inflate(R.layout.layout, null); + TextView tv = layout.findViewById(R.id.text_view_id); + assertNotNull(tv); + assertEquals("Overlaid", tv.getText().toString()); + } +} diff --git a/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/Android.bp b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/Android.bp new file mode 100644 index 000000000000..b5e6d9c692bd --- /dev/null +++ b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/Android.bp @@ -0,0 +1,30 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "OverlayDeviceTestsNonSystem_AppOverlay", + team: "trendy_team_android_resources", + sdk_version: "current", + certificate: "platform", + aaptflags: ["--no-resource-removal"], +} diff --git a/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/AndroidManifest.xml b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..4df80c085602 --- /dev/null +++ b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/AndroidManifest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.non_system.app_overlay" + android:versionCode="1" + android:versionName="1.0"> + <application android:hasCode="false" /> + <overlay android:targetPackage="com.android.overlaytest.non_system" + android:targetName="TestResources" + android:isStatic="true" + android:resourcesMap="@xml/overlays"/> +</manifest>
\ No newline at end of file diff --git a/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/res/xml/overlays.xml b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/res/xml/overlays.xml new file mode 100644 index 000000000000..d0d4bfed94ed --- /dev/null +++ b/core/tests/overlaytests/device_non_system/test-apps/AppOverlay/res/xml/overlays.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<overlay> + <item target="string/test_string" value="Overlaid"/> +</overlay>
\ No newline at end of file diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 6cf12deea928..483b6934ee8c 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1045,12 +1045,6 @@ "group": "WM_DEBUG_BACK_PREVIEW", "at": "com\/android\/server\/wm\/BackNavigationController.java" }, - "-1459414342866553129": { - "message": "Current focused window being animated by recents. Overriding back callback to recents controller callback.", - "level": "DEBUG", - "group": "WM_DEBUG_BACK_PREVIEW", - "at": "com\/android\/server\/wm\/BackNavigationController.java" - }, "2881085074175114605": { "message": "Focused window didn't have a valid surface drawn.", "level": "DEBUG", diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index e73d8802f0b2..8487e3792993 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles import android.content.Context import android.content.Intent import android.content.pm.ShortcutInfo +import android.content.res.Resources import android.graphics.Insets import android.graphics.PointF import android.graphics.Rect @@ -43,6 +44,9 @@ class BubblePositionerTest { private lateinit var positioner: BubblePositioner private val context = ApplicationProvider.getApplicationContext<Context>() + private val resources: Resources + get() = context.resources + private val defaultDeviceConfig = DeviceConfig( windowBounds = Rect(0, 0, 1000, 2000), @@ -205,6 +209,58 @@ class BubblePositionerTest { } @Test + fun testBubbleBarExpandedViewHeightAndWidth() { + val deviceConfig = + defaultDeviceConfig.copy( + // portrait orientation + isLandscape = false, + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, 1800, 2600) + ) + val bubbleBarBounds = Rect(1700, 2500, 1780, 2600) + + positioner.setShowingInBubbleBar(true) + positioner.update(deviceConfig) + positioner.bubbleBarBounds = bubbleBarBounds + + val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680 + val expandedViewVerticalSpacing = + resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) + val expectedHeight = + spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewVerticalSpacing + val expectedWidth = resources.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width) + + assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth) + assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight) + } + + @Test + fun testBubbleBarExpandedViewHeightAndWidth_screenWidthTooSmall() { + val screenWidth = 300 + val deviceConfig = + defaultDeviceConfig.copy( + // portrait orientation + isLandscape = false, + isLargeScreen = true, + insets = Insets.of(10, 20, 5, 15), + windowBounds = Rect(0, 0, screenWidth, 2600) + ) + val bubbleBarBounds = Rect(100, 2500, 280, 2550) + positioner.setShowingInBubbleBar(true) + positioner.update(deviceConfig) + positioner.bubbleBarBounds = bubbleBarBounds + + val spaceBetweenTopInsetAndBubbleBarInLandscape = 180 + val expandedViewSpacing = + resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) + val expectedHeight = spaceBetweenTopInsetAndBubbleBarInLandscape - 2 * expandedViewSpacing + val expectedWidth = screenWidth - 15 /* horizontal insets */ - 2 * expandedViewSpacing + assertThat(positioner.getExpandedViewWidthForBubbleBar(false)).isEqualTo(expectedWidth) + assertThat(positioner.getExpandedViewHeightForBubbleBar(false)).isEqualTo(expectedHeight) + } + + @Test fun testGetExpandedViewHeight_max() { val deviceConfig = defaultDeviceConfig.copy( diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml index ef7478c04dda..c0ff1922edc8 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml @@ -22,14 +22,15 @@ android:layout_height="wrap_content" android:gravity="center_horizontal"> - <ImageButton + <com.android.wm.shell.windowdecor.HandleImageButton android:id="@+id/caption_handle" android:layout_width="@dimen/desktop_mode_fullscreen_decor_caption_width" android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height" android:paddingVertical="16dp" + android:paddingHorizontal="10dp" android:contentDescription="@string/handle_text" android:src="@drawable/decor_handle_dark" tools:tint="@color/desktop_mode_caption_handle_bar_dark" android:scaleType="fitXY" - android:background="?android:selectableItemBackground"/> + android:background="@android:color/transparent"/> </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 39dd4d3af98d..4ee2c1a1da60 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -254,6 +254,8 @@ <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen> <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. --> <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen> + <!-- Width of the expanded bubble bar view shown when the bubble is expanded. --> + <dimen name="bubble_bar_expanded_view_width">412dp</dimen> <!-- Minimum width of the bubble bar manage menu. --> <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen> <!-- Size of the dismiss icon in the bubble bar manage menu. --> @@ -423,8 +425,9 @@ <!-- Height of desktop mode caption for fullscreen tasks. --> <dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen> - <!-- Width of desktop mode caption for fullscreen tasks. --> - <dimen name="desktop_mode_fullscreen_decor_caption_width">128dp</dimen> + <!-- Width of desktop mode caption for fullscreen tasks. + 80 dp for handle + 20 dp for room to grow on the sides when hovered. --> + <dimen name="desktop_mode_fullscreen_decor_caption_width">100dp</dimen> <!-- Required empty space to be visible for partially offscreen tasks. --> <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 4d5e516f76e5..14c3a0701c83 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -149,9 +149,10 @@ public class BubblePositioner { mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); if (mShowingInBubbleBar) { - mExpandedViewLargeScreenWidth = isLandscape() - ? (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_LANDSCAPE_WIDTH_PERCENT) - : (int) (bounds.width() * EXPANDED_VIEW_BUBBLE_BAR_PORTRAIT_WIDTH_PERCENT); + mExpandedViewLargeScreenWidth = Math.min( + res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width), + mPositionRect.width() - 2 * mExpandedViewPadding + ); } else if (mDeviceConfig.isSmallTablet()) { mExpandedViewLargeScreenWidth = (int) (bounds.width() * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT); @@ -839,11 +840,42 @@ public class BubblePositioner { * How tall the expanded view should be when showing from the bubble bar. */ public int getExpandedViewHeightForBubbleBar(boolean isOverflow) { - return isOverflow - ? mOverflowHeight - : getExpandedViewBottomForBubbleBar() - mInsets.top - mExpandedViewPadding; + if (isOverflow) { + return mOverflowHeight; + } else { + return getBubbleBarExpandedViewHeightForLandscape(); + } } + /** + * Calculate the height of expanded view in landscape mode regardless current orientation. + * Here is an explanation: + * ------------------------ mScreenRect.top + * | top inset ↕ | + * |----------------------- + * | 16dp spacing ↕ | + * | --------- | --- expanded view top + * | | | | ↑ + * | | | | ↓ expanded view height + * | --------- | --- expanded view bottom + * | 16dp spacing ↕ | ↑ + * | @bubble bar@ | | height of the bubble bar container + * ------------------------ | already includes bottom inset and spacing + * | bottom inset ↕ | ↓ + * |----------------------| --- mScreenRect.bottom + */ + private int getBubbleBarExpandedViewHeightForLandscape() { + int heightOfBubbleBarContainer = + mScreenRect.height() - getExpandedViewBottomForBubbleBar(); + // getting landscape height from screen rect + int expandedViewHeight = Math.min(mScreenRect.width(), mScreenRect.height()); + expandedViewHeight -= heightOfBubbleBarContainer; /* removing bubble container height */ + expandedViewHeight -= mInsets.top; /* removing top inset */ + expandedViewHeight -= mExpandedViewPadding; /* removing spacing */ + return expandedViewHeight; + } + + /** The bottom position of the expanded view when showing above the bubble bar. */ public int getExpandedViewBottomForBubbleBar() { return mBubbleBarBounds.top - mExpandedViewPadding; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index e210ea731f7a..58942ec92a71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -1354,6 +1354,13 @@ class DesktopTasksController( "setTaskListener" ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() } } + + override fun moveToDesktop(taskId: Int) { + ExecutorUtils.executeRemoteCallWithTaskPermission( + controller, + "moveToDesktop" + ) { c -> c.moveToDesktop(taskId) } + } } companion object { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 6bdaf1eadb8a..fa4352241193 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -45,4 +45,7 @@ interface IDesktopMode { /** Set listener that will receive callbacks about updates to desktop tasks */ oneway void setTaskListener(IDesktopTaskListener listener); + + /** Move a task with given `taskId` to desktop */ + void moveToDesktop(int taskId); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 74e85f8dd468..9adb67c8a65e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -507,6 +507,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final Point animRelOffset = new Point( change.getEndAbsBounds().left - animRoot.getOffset().x, change.getEndAbsBounds().top - animRoot.getOffset().y); + + if (change.getActivityComponent() != null) { + // For appcompat letterbox: we intentionally report the task-bounds so that we + // can animate as-if letterboxes are "part of" the activity. This means we can't + // always rely solely on endAbsBounds and need to also max with endRelOffset. + animRelOffset.x = Math.max(animRelOffset.x, change.getEndRelOffset().x); + animRelOffset.y = Math.max(animRelOffset.y, change.getEndRelOffset().y); + } + if (change.getActivityComponent() != null && !isActivityLevel) { // At this point, this is an independent activity change in a non-activity // transition. This means that an activity transition got erroneously combined 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 c26604a84a61..7c2ba455c0c9 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 @@ -293,7 +293,13 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public void onFoldStateChanged(boolean isFolded) { if (isFolded) { + // Reset unfold animation finished flag on folding, so it could be used next time + // when we unfold the device as an indication that animation hasn't finished yet mAnimationFinished = false; + + // If we are currently animating unfold animation we should finish it because + // the animation might not start and finish as the device was folded + finishTransitionIfNeeded(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index d0879434657d..963b1303c379 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.windowingModeToString; +import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; @@ -28,6 +29,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; @@ -39,6 +41,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.util.Log; import android.view.Choreographer; import android.view.MotionEvent; import android.view.SurfaceControl; @@ -433,15 +436,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } private void loadAppInfo() { + final ActivityInfo activityInfo = mTaskInfo.topActivityInfo; + if (activityInfo == null) { + Log.e(TAG, "Top activity info not found in task"); + return; + } PackageManager pm = mContext.getApplicationContext().getPackageManager(); final IconProvider provider = new IconProvider(mContext); - mAppIconDrawable = provider.getIcon(mTaskInfo.topActivityInfo); + mAppIconDrawable = provider.getIcon(activityInfo); final Resources resources = mContext.getResources(); final BaseIconFactory factory = new BaseIconFactory(mContext, resources.getDisplayMetrics().densityDpi, resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius)); mAppIconBitmap = factory.createScaledBitmap(mAppIconDrawable, MODE_DEFAULT); - final ApplicationInfo applicationInfo = mTaskInfo.topActivityInfo.applicationInfo; + final ApplicationInfo applicationInfo = activityInfo.applicationInfo; mAppName = pm.getApplicationLabel(applicationInfo); } @@ -752,7 +760,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final int action = ev.getActionMasked(); // The comparison against ACTION_UP is needed for the cancel drag to desktop case. handle.setHovered(inHandle && action != ACTION_UP); - handle.setPressed(inHandle && action == ACTION_DOWN); + // We want handle to remain pressed if the pointer moves outside of it during a drag. + handle.setPressed((inHandle && action == ACTION_DOWN) + || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL)); if (isHandleMenuActive()) { mHandleMenu.checkMotionEvent(ev); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt new file mode 100644 index 000000000000..b21c3f522eab --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleImageButton.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.windowdecor + +import android.animation.ValueAnimator +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageButton + +/** + * [ImageButton] for the handle at the top of fullscreen apps. Has custom hover + * and press handling to grow the handle on hover enter and shrink the handle on + * hover exit and press. + */ +class HandleImageButton (context: Context?, attrs: AttributeSet?) : + ImageButton(context, attrs) { + private val handleAnimator = ValueAnimator() + + override fun onHoverChanged(hovered: Boolean) { + super.onHoverChanged(hovered) + if (hovered) { + animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_HOVER_ENTER_SCALE) + } else { + if (!isPressed) { + animateHandle(HANDLE_HOVER_ANIM_DURATION, HANDLE_DEFAULT_SCALE) + } + } + } + + override fun setPressed(pressed: Boolean) { + if (isPressed != pressed) { + super.setPressed(pressed) + if (pressed) { + animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_PRESS_DOWN_SCALE) + } else { + animateHandle(HANDLE_PRESS_ANIM_DURATION, HANDLE_DEFAULT_SCALE) + } + } + } + + private fun animateHandle(duration: Long, endScale: Float) { + if (handleAnimator.isRunning) { + handleAnimator.cancel() + } + handleAnimator.duration = duration + handleAnimator.setFloatValues(scaleX, endScale) + handleAnimator.addUpdateListener { animator -> + scaleX = animator.animatedValue as Float + } + handleAnimator.start() + } + + companion object { + /** The duration of animations related to hover state. **/ + private const val HANDLE_HOVER_ANIM_DURATION = 300L + /** The duration of animations related to pressed state. **/ + private const val HANDLE_PRESS_ANIM_DURATION = 200L + /** Ending scale for hover enter. **/ + private const val HANDLE_HOVER_ENTER_SCALE = 1.2f + /** Ending scale for press down. **/ + private const val HANDLE_PRESS_DOWN_SCALE = 0.85f + /** Default scale for handle. **/ + private const val HANDLE_DEFAULT_SCALE = 1f + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt index 2fda3ea8daee..7898567b70e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt @@ -21,7 +21,7 @@ import android.view.MotionEvent import android.widget.ImageButton /** - * A custom [ImageButton] that intentionally does not handle hover events. + * A custom [ImageButton] for buttons inside handle menu that intentionally doesn't handle hovers. * This is due to the hover events being handled by [DesktopModeWindowDecorViewModel] * in order to take the status bar layer into account. Handling it in both classes results in a * flicker when the hover moves from outside to inside status bar layer. 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 c5e229feaba7..acc0bce5cce9 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 @@ -298,6 +298,32 @@ public class UnfoldTransitionHandlerTest { } @Test + public void fold_animationInProgress_finishesTransition() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + + // Unfold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback + ); + + // Start animation but don't finish it + mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeProgress(0.5f); + + // Fold + mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true); + + verify(finishCallback).onTransitionFinished(any()); + } + + @Test public void mergeAnimation_eatsDisplayOnlyTransitions() { TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 29bb1b9846b4..4706699039a6 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -538,6 +538,7 @@ cc_defaults { "pipeline/skia/RenderNodeDrawable.cpp", "pipeline/skia/ReorderBarrierDrawables.cpp", "pipeline/skia/TransformCanvas.cpp", + "renderstate/RenderState.cpp", "renderthread/Frame.cpp", "renderthread/RenderTask.cpp", "renderthread/TimeLord.cpp", @@ -615,7 +616,6 @@ cc_defaults { "pipeline/skia/SkiaVulkanPipeline.cpp", "pipeline/skia/VkFunctorDrawable.cpp", "pipeline/skia/VkInteropFunctorDrawable.cpp", - "renderstate/RenderState.cpp", "renderthread/CacheManager.cpp", "renderthread/CanvasContext.cpp", "renderthread/DrawFrameTask.cpp", diff --git a/libs/hwui/platform/host/renderthread/RenderThread.cpp b/libs/hwui/platform/host/renderthread/RenderThread.cpp index 6f08b5979772..f9d0f4704e08 100644 --- a/libs/hwui/platform/host/renderthread/RenderThread.cpp +++ b/libs/hwui/platform/host/renderthread/RenderThread.cpp @@ -17,6 +17,7 @@ #include "renderthread/RenderThread.h" #include "Readback.h" +#include "renderstate/RenderState.h" #include "renderthread/VulkanManager.h" namespace android { @@ -66,6 +67,7 @@ RenderThread::RenderThread() RenderThread::~RenderThread() {} void RenderThread::initThreadLocals() { + mRenderState = new RenderState(*this); mCacheManager = new CacheManager(*this); } diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index e08d32a7735c..60657cf91123 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -16,11 +16,13 @@ #ifndef RENDERSTATE_H #define RENDERSTATE_H -#include "utils/Macros.h" - +#include <pthread.h> #include <utils/RefBase.h> + #include <set> +#include "utils/Macros.h" + namespace android { namespace uirenderer { diff --git a/nfc/Android.bp b/nfc/Android.bp index c186804d2006..13ac2311bde3 100644 --- a/nfc/Android.bp +++ b/nfc/Android.bp @@ -66,6 +66,7 @@ java_sdk_library { ], impl_library_visibility: [ "//frameworks/base:__subpackages__", + "//cts/hostsidetests/multidevices/nfc:__subpackages__", "//cts/tests/tests/nfc", "//packages/apps/Nfc:__subpackages__", ], diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index 0af108052137..e8e24f492005 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -40,6 +40,7 @@ <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:gravity">center</item> + <item name="android:textDirection">locale</item> <item name="android:layout_marginLeft">14dp</item> <item name="android:layout_marginRight">14dp</item> <item name="android:textSize">20sp</item> @@ -53,6 +54,7 @@ <item name="android:layout_marginTop">18dp</item> <item name="android:layout_marginLeft">18dp</item> <item name="android:layout_marginRight">18dp</item> + <item name="android:textDirection">locale</item> <item name="android:textSize">14sp</item> <item name="android:textColor">?android:attr/textColorSecondary</item> </style> diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt index b408c1553d94..9c8ec3b56813 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -142,7 +142,7 @@ private fun getCredentialOptionInfoList( isDefaultIconPreferredAsSingleProvider = credentialEntry.isDefaultIconPreferredAsSingleProvider, affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), - biometricRequest = predetermineAndValidateBiometricFlow(it, + biometricRequest = retrieveEntryBiometricRequest(it, CREDENTIAL_ENTRY_PREFIX), ) ) @@ -172,7 +172,7 @@ private fun getCredentialOptionInfoList( isDefaultIconPreferredAsSingleProvider = credentialEntry.isDefaultIconPreferredAsSingleProvider, affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), - biometricRequest = predetermineAndValidateBiometricFlow(it, + biometricRequest = retrieveEntryBiometricRequest(it, CREDENTIAL_ENTRY_PREFIX), ) ) @@ -201,7 +201,7 @@ private fun getCredentialOptionInfoList( isDefaultIconPreferredAsSingleProvider = credentialEntry.isDefaultIconPreferredAsSingleProvider, affiliatedDomain = credentialEntry.affiliatedDomain?.toString(), - biometricRequest = predetermineAndValidateBiometricFlow(it, + biometricRequest = retrieveEntryBiometricRequest(it, CREDENTIAL_ENTRY_PREFIX), ) ) @@ -216,7 +216,7 @@ private fun getCredentialOptionInfoList( } /** - * This validates if this is a biometric flow or not, and if it is, this returns the expected + * This validates if the entry calling this method contains biometric info, and if so, returns a * [BiometricRequestInfo]. Namely, the biometric flow must have at least the * ALLOWED_AUTHENTICATORS bit passed from Jetpack. * Note that the required values, such as the provider info's icon or display name, or the entries @@ -230,7 +230,7 @@ private fun getCredentialOptionInfoList( * // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never * // expected to be used. When it is added, it should be lightly validated. */ -fun predetermineAndValidateBiometricFlow( +fun retrieveEntryBiometricRequest( entry: Entry, hintPrefix: String, ): BiometricRequestInfo? { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index a03975375e8a..888777e81d65 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -30,6 +30,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import com.android.credentialmanager.common.BiometricFlowType +import com.android.credentialmanager.common.BiometricPromptState import com.android.credentialmanager.common.BiometricResult import com.android.credentialmanager.common.BiometricState import com.android.credentialmanager.model.EntryInfo @@ -40,7 +42,7 @@ import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.createflow.ActiveEntry import com.android.credentialmanager.createflow.CreateCredentialUiState import com.android.credentialmanager.createflow.CreateScreenState -import com.android.credentialmanager.createflow.findBiometricFlowEntry +import com.android.credentialmanager.createflow.isBiometricFlow import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.GetScreenState import com.android.credentialmanager.logging.LifecycleEvent @@ -303,13 +305,23 @@ class CredentialSelectorViewModel( } fun createFlowOnEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) { + val isBiometricFlow = isBiometricFlow(activeEntry = activeEntry, isAutoSelectFlow = false) + if (isBiometricFlow) { + // This atomically ensures that the only edge case that *restarts* the biometric flow + // doesn't risk a configuration change bug on the more options page during create. + // Namely, it's atomic in that it happens only on a tap, and it is not possible to + // reproduce a tap and a rotation at the same time. However, even if it were, it would + // just be an alternate way to jump back into the biometric selection flow after this + // reset, and thus, the state machine is maintained. + onBiometricPromptStateChange(BiometricPromptState.INACTIVE) + } uiState = uiState.copy( createCredentialUiState = uiState.createCredentialUiState?.copy( currentScreenState = // An autoselect flow never makes it to the more options screen - if (findBiometricFlowEntry(activeEntry = activeEntry, - isAutoSelectFlow = false) != null) CreateScreenState.BIOMETRIC_SELECTION - else if ( + if (isBiometricFlow) { + CreateScreenState.BIOMETRIC_SELECTION + } else if ( uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds ?.contains(activeEntry.activeProvider.id) ?: true || !(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider @@ -375,6 +387,46 @@ class CredentialSelectorViewModel( } } + /**************************************************************************/ + /***** Biometric Flow Callbacks *****/ + /**************************************************************************/ + + /** + * This allows falling back from the biometric prompt screen to the normal get flow by applying + * a reset to all necessary states involved in the fallback. + */ + fun fallbackFromBiometricToNormalFlow(biometricFlowType: BiometricFlowType) { + onBiometricPromptStateChange(BiometricPromptState.INACTIVE) + when (biometricFlowType) { + BiometricFlowType.GET -> getFlowOnBackToPrimarySelectionScreen() + BiometricFlowType.CREATE -> createFlowOnUseOnceSelected() + } + } + + /** + * This method can be used to change the [BiometricPromptState] according to the necessity. + * For example, if resetting, one might use [BiometricPromptState.INACTIVE], but if the flow + * has just launched, to avoid configuration errors, one can use + * [BiometricPromptState.PENDING]. + */ + fun onBiometricPromptStateChange(biometricPromptState: BiometricPromptState) { + uiState = uiState.copy( + biometricState = uiState.biometricState.copy( + biometricStatus = biometricPromptState + ) + ) + } + + /** + * This returns the present biometric state. + */ + fun getBiometricPromptState(): BiometricPromptState = + uiState.biometricState.biometricStatus + + /**************************************************************************/ + /***** Misc. Callbacks/Logs *****/ + /**************************************************************************/ + @Composable fun logUiEvent(uiEventEnum: UiEventEnum) { this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 358ebfa1ec90..c477f30a1d2f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -53,8 +53,9 @@ import androidx.credentials.provider.RemoteEntry import org.json.JSONObject import android.credentials.flags.Flags import com.android.credentialmanager.createflow.isBiometricFlow +import com.android.credentialmanager.createflow.isFlowAutoSelectable import com.android.credentialmanager.getflow.TopBrandingContent -import com.android.credentialmanager.ktx.predetermineAndValidateBiometricFlow +import com.android.credentialmanager.ktx.retrieveEntryBiometricRequest import java.time.Instant fun getAppLabel( @@ -431,7 +432,12 @@ class CreateFlowUtils { remoteEntryProvider = remoteEntryProvider, ) val isBiometricFlow = if (activeEntry == null) false else isBiometricFlow(activeEntry, - sortedCreateOptionsPairs, requestDisplayInfo) + isFlowAutoSelectable( + requestDisplayInfo = requestDisplayInfo, + activeEntry = activeEntry, + sortedCreateOptionsPairs = sortedCreateOptionsPairs + ) + ) val initialScreenState = toCreateScreenState( createOptionSize = createOptionsPairs.size, remoteEntry = remoteEntry, @@ -514,7 +520,7 @@ class CreateFlowUtils { it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" + "SELECT_ALLOWED") }?.text == "true", - biometricRequest = predetermineAndValidateBiometricFlow(it, + biometricRequest = retrieveEntryBiometricRequest(it, CREATE_ENTRY_PREFIX), ) ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricFlowType.kt index f6140f51b7b5..263cfd574d73 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricFlowType.kt @@ -16,7 +16,7 @@ package com.android.credentialmanager.common -enum class FlowType { +enum class BiometricFlowType { GET, CREATE }
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt index fa177351be30..be3e0437a83e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt @@ -59,7 +59,7 @@ import java.lang.Exception * ). * * The above are examples; the credential type can change depending on scenario. - * // TODO(b/326243891) : Finalize once all the strings and create flow is iterated to completion + * // TODO(b/333445112) : Finalize once all the strings and create flow is iterated to completion */ data class BiometricDisplayInfo( val providerIcon: Bitmap, @@ -75,7 +75,8 @@ data class BiometricDisplayInfo( * additional states that may improve the flow. */ data class BiometricState( - val biometricResult: BiometricResult? = null + val biometricResult: BiometricResult? = null, + val biometricStatus: BiometricPromptState = BiometricPromptState.INACTIVE ) /** @@ -104,58 +105,115 @@ data class BiometricHelp( ) /** - * This will handle the logic for integrating credential manager with the biometric prompt for the - * single account biometric experience. This simultaneously handles both the get and create flows, - * by retrieving all the data from credential manager, and properly parsing that data into the - * biometric prompt. + * This is the entry point to start the integrated biometric prompt for 'get' flows. It captures + * information specific to the get flow, along with required shared callbacks and more general + * info across both flows, such as the tapped [EntryInfo] or [sendDataToProvider]. */ -fun runBiometricFlow( +fun runBiometricFlowForGet( biometricEntry: EntryInfo, context: Context, openMoreOptionsPage: () -> Unit, sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit, onCancelFlowAndFinish: () -> Unit, onIllegalStateAndFinish: (String) -> Unit, + getBiometricPromptState: () -> BiometricPromptState, + onBiometricPromptStateChange: (BiometricPromptState) -> Unit, + onBiometricFailureFallback: (BiometricFlowType) -> Unit, getRequestDisplayInfo: RequestDisplayInfo? = null, getProviderInfoList: List<ProviderInfo>? = null, getProviderDisplayInfo: ProviderDisplayInfo? = null, - onBiometricFailureFallback: () -> Unit, +) { + if (getBiometricPromptState() != BiometricPromptState.INACTIVE) { + // Screen is already up, do not re-launch + return + } + onBiometricPromptStateChange(BiometricPromptState.PENDING) + val biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo( + getRequestDisplayInfo, + getProviderInfoList, + getProviderDisplayInfo, + context, biometricEntry + ) + + if (biometricDisplayInfo == null) { + onBiometricFailureFallback(BiometricFlowType.GET) + return + } + + val callback: BiometricPrompt.AuthenticationCallback = + setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry, + onCancelFlowAndFinish, onIllegalStateAndFinish, onBiometricPromptStateChange) + + Log.d(TAG, "The BiometricPrompt API call begins.") + runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage, + onBiometricFailureFallback, BiometricFlowType.GET) +} + +/** + * This is the entry point to start the integrated biometric prompt for 'create' flows. It captures + * information specific to the create flow, along with required shared callbacks and more general + * info across both flows, such as the tapped [EntryInfo] or [sendDataToProvider]. + */ +fun runBiometricFlowForCreate( + biometricEntry: EntryInfo, + context: Context, + openMoreOptionsPage: () -> Unit, + sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit, + onCancelFlowAndFinish: () -> Unit, + onIllegalStateAndFinish: (String) -> Unit, + getBiometricPromptState: () -> BiometricPromptState, + onBiometricPromptStateChange: (BiometricPromptState) -> Unit, + onBiometricFailureFallback: (BiometricFlowType) -> Unit, createRequestDisplayInfo: com.android.credentialmanager.createflow .RequestDisplayInfo? = null, createProviderInfo: EnabledProviderInfo? = null, ) { - // TODO(b/330396089) : Add rotation configuration fix with state machine - var biometricDisplayInfo: BiometricDisplayInfo? = null - var flowType = FlowType.GET - if (getRequestDisplayInfo != null) { - biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(getRequestDisplayInfo, - getProviderInfoList, - getProviderDisplayInfo, - context, biometricEntry) - } else if (createRequestDisplayInfo != null) { - flowType = FlowType.CREATE - biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo( - createRequestDisplayInfo, - createProviderInfo, - context, biometricEntry) + if (getBiometricPromptState() != BiometricPromptState.INACTIVE) { + // Screen is already up, do not re-launch + return } + onBiometricPromptStateChange(BiometricPromptState.PENDING) + val biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo( + createRequestDisplayInfo, + createProviderInfo, + context, biometricEntry + ) if (biometricDisplayInfo == null) { - onBiometricFailureFallback() + onBiometricFailureFallback(BiometricFlowType.CREATE) return } - val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage, - biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators, flowType) - val callback: BiometricPrompt.AuthenticationCallback = setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry, - onCancelFlowAndFinish, onIllegalStateAndFinish) + onCancelFlowAndFinish, onIllegalStateAndFinish, onBiometricPromptStateChange) + + Log.d(TAG, "The BiometricPrompt API call begins.") + runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage, + onBiometricFailureFallback, BiometricFlowType.CREATE) +} + +/** + * This will handle the logic for integrating credential manager with the biometric prompt for the + * single account biometric experience. This simultaneously handles both the get and create flows, + * by retrieving all the data from credential manager, and properly parsing that data into the + * biometric prompt. + */ +private fun runBiometricFlow( + context: Context, + biometricDisplayInfo: BiometricDisplayInfo, + callback: BiometricPrompt.AuthenticationCallback, + openMoreOptionsPage: () -> Unit, + onBiometricFailureFallback: (BiometricFlowType) -> Unit, + biometricFlowType: BiometricFlowType +) { + val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage, + biometricDisplayInfo.biometricRequestInfo, biometricFlowType) val cancellationSignal = CancellationSignal() cancellationSignal.setOnCancelListener { Log.d(TAG, "Your cancellation signal was called.") - // TODO(b/326243754) : Migrate towards passing along the developer cancellation signal + // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal // or validate the necessity for this } @@ -165,27 +223,28 @@ fun runBiometricFlow( biometricPrompt.authenticate(cancellationSignal, executor, callback) } catch (e: IllegalArgumentException) { Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n") - onBiometricFailureFallback() + onBiometricFailureFallback(biometricFlowType) } } /** * Sets up the biometric prompt with the UI specific bits. - * // TODO(b/326243754) : Pass in opId once dependency is confirmed via CryptoObject + * // TODO(b/333445112) : Pass in opId once dependency is confirmed via CryptoObject */ private fun setupBiometricPrompt( context: Context, biometricDisplayInfo: BiometricDisplayInfo, openMoreOptionsPage: () -> Unit, - requestAllowedAuthenticators: Int, - flowType: FlowType, + biometricRequestInfo: BiometricRequestInfo, + biometricFlowType: BiometricFlowType, ): BiometricPrompt { - val finalAuthenticators = removeDeviceCredential(requestAllowedAuthenticators) + val finalAuthenticators = removeDeviceCredential(biometricRequestInfo.allowedAuthenticators) val biometricPrompt = BiometricPrompt.Builder(context) .setTitle(biometricDisplayInfo.displayTitleText) - // TODO(b/326243754) : Migrate to using new methods recently aligned upon - .setNegativeButton(context.getString(if (flowType == FlowType.GET) R.string + // TODO(b/333445112) : Migrate to using new methods and strings recently aligned upon + .setNegativeButton(context.getString(if (biometricFlowType == BiometricFlowType.GET) + R.string .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options), getMainExecutor(context)) { _, _ -> openMoreOptionsPage() @@ -200,7 +259,7 @@ private fun setupBiometricPrompt( return biometricPrompt } -// TODO(b/326243754) : Remove after larger level alignments made on fallback negative button +// TODO(b/333445112) : Remove after larger level alignments made on fallback negative button // For the time being, we do not support the pin fallback until UX is decided. private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int { var finalAuthenticators = requestAllowedAuthenticators @@ -230,16 +289,18 @@ private fun setupBiometricAuthenticationCallback( selectedEntry: EntryInfo, onCancelFlowAndFinish: () -> Unit, onIllegalStateAndFinish: (String) -> Unit, + onBiometricPromptStateChange: (BiometricPromptState) -> Unit ): BiometricPrompt.AuthenticationCallback { val callback: BiometricPrompt.AuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() { - // TODO(b/326243754) : Validate remaining callbacks + // TODO(b/333445772) : Validate remaining callbacks override fun onAuthenticationSucceeded( authResult: BiometricPrompt.AuthenticationResult? ) { super.onAuthenticationSucceeded(authResult) try { if (authResult != null) { + onBiometricPromptStateChange(BiometricPromptState.COMPLETE) sendDataToProvider(selectedEntry, authResult) } else { onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " + @@ -254,26 +315,24 @@ private fun setupBiometricAuthenticationCallback( override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) { super.onAuthenticationHelp(helpCode, helpString) Log.d(TAG, "Authentication help discovered: $helpCode and $helpString") - // TODO(b/326243754) : Decide on strategy with provider (a simple log probably - // suffices here) } override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { super.onAuthenticationError(errorCode, errString) Log.d(TAG, "Authentication error-ed out: $errorCode and $errString") + onBiometricPromptStateChange(BiometricPromptState.COMPLETE) if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) { // Note that because the biometric prompt is imbued directly // into the selector, parity applies to the selector's cancellation instead // of the provider's biometric prompt cancellation. onCancelFlowAndFinish() } - // TODO(b/326243754) : Propagate to provider + // TODO(b/333445772) : Propagate to provider } override fun onAuthenticationFailed() { super.onAuthenticationFailed() Log.d(TAG, "Authentication failed.") - // TODO(b/326243754) : Propagate to provider } } return callback @@ -299,7 +358,7 @@ private fun validateAndRetrieveBiometricGetDisplayInfo( if (getRequestDisplayInfo != null && getProviderInfoList != null && getProviderDisplayInfo != null) { if (selectedEntry !is CredentialEntryInfo) { return null } - return getBiometricDisplayValues(getProviderInfoList, + return retrieveBiometricGetDisplayValues(getProviderInfoList, context, getRequestDisplayInfo, selectedEntry) } return null @@ -308,7 +367,8 @@ private fun validateAndRetrieveBiometricGetDisplayInfo( /** * Creates the [BiometricDisplayInfo] for create flows, and early handles conditional * checking between the two. The reason for this method matches the logic for the - * [validateBiometricGetFlow] with the only difference being that this is for the create flow. + * [validateAndRetrieveBiometricGetDisplayInfo] with the only difference being that this is for + * the create flow. */ private fun validateAndRetrieveBiometricCreateDisplayInfo( createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo?, @@ -318,8 +378,8 @@ private fun validateAndRetrieveBiometricCreateDisplayInfo( ): BiometricDisplayInfo? { if (createRequestDisplayInfo != null && createProviderInfo != null) { if (selectedEntry !is CreateOptionInfo) { return null } - return createBiometricDisplayValues(createRequestDisplayInfo, createProviderInfo, context, - selectedEntry) + return retrieveBiometricCreateDisplayValues(createRequestDisplayInfo, createProviderInfo, + context, selectedEntry) } return null } @@ -330,16 +390,16 @@ private fun validateAndRetrieveBiometricCreateDisplayInfo( * to the original selector. Note that these redundant checks are just failsafe; the original * flow should never reach here with invalid params. */ -private fun getBiometricDisplayValues( +private fun retrieveBiometricGetDisplayValues( getProviderInfoList: List<ProviderInfo>, context: Context, getRequestDisplayInfo: RequestDisplayInfo, selectedEntry: CredentialEntryInfo, ): BiometricDisplayInfo? { - var icon: Bitmap? = null - var providerName: String? = null - var displayTitleText: String? = null - var descriptionText: String? = null + val icon: Bitmap? + val providerName: String? + val displayTitleText: String? + val descriptionText: String? val primaryAccountsProviderInfo = retrievePrimaryAccountProviderInfo(selectedEntry.providerId, getProviderInfoList) icon = primaryAccountsProviderInfo?.icon?.toBitmap() @@ -373,7 +433,7 @@ private fun getBiometricDisplayValues( * if this is called, a result is guaranteed. Specifically, this is guaranteed to return a non-null * value unlike the get counterpart. */ -private fun createBiometricDisplayValues( +private fun retrieveBiometricCreateDisplayValues( createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo, createProviderInfo: EnabledProviderInfo, context: Context, @@ -401,7 +461,7 @@ private fun createBiometricDisplayValues( }, createRequestDisplayInfo.appName, ) - // TODO(b/327620327) : Add a subtitle and any other recently aligned ideas + // TODO(b/333445112) : Add a subtitle and any other recently aligned ideas return BiometricDisplayInfo(providerIcon = icon, providerName = providerName, displayTitleText = displayTitleText, descriptionForCredential = descriptionText, biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.kt new file mode 100644 index 000000000000..e1aa0418e7a0 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricPromptState.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.credentialmanager.common + +enum class BiometricPromptState { + /** The biometric prompt hasn't been activated. */ + INACTIVE, + /** The biometric prompt is active but data hasn't been returned yet. */ + PENDING, + /** The biometric prompt has closed and returned data we then send to the provider activity. */ + COMPLETE +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 25fb477cbf38..122b8964dc96 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -46,11 +46,13 @@ import androidx.core.graphics.drawable.toBitmap import com.android.compose.theme.LocalAndroidColorScheme import com.android.credentialmanager.CredentialSelectorViewModel import com.android.credentialmanager.R +import com.android.credentialmanager.common.BiometricFlowType +import com.android.credentialmanager.common.BiometricPromptState import com.android.credentialmanager.model.EntryInfo import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.material.ModalBottomSheetDefaults -import com.android.credentialmanager.common.runBiometricFlow +import com.android.credentialmanager.common.runBiometricFlowForCreate import com.android.credentialmanager.common.ui.ActionButton import com.android.credentialmanager.common.ui.BodyMediumText import com.android.credentialmanager.common.ui.BodySmallText @@ -111,7 +113,11 @@ fun CreateCredentialScreen( onBiometricEntrySelected = viewModel::createFlowOnEntrySelected, fallbackToOriginalFlow = - viewModel::getFlowOnBackToPrimarySelectionScreen, + viewModel::fallbackFromBiometricToNormalFlow, + getBiometricPromptState = + viewModel::getBiometricPromptState, + onBiometricPromptStateChange = + viewModel::onBiometricPromptStateChange ) CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard( requestDisplayInfo = createCredentialUiState.requestDisplayInfo, @@ -578,18 +584,22 @@ internal fun BiometricSelectionPage( onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit, onCancelFlowAndFinish: () -> Unit, onIllegalScreenStateAndFinish: (String) -> Unit, - fallbackToOriginalFlow: () -> Unit, + fallbackToOriginalFlow: (BiometricFlowType) -> Unit, + getBiometricPromptState: () -> BiometricPromptState, + onBiometricPromptStateChange: (BiometricPromptState) -> Unit, ) { if (biometricEntry == null) { - fallbackToOriginalFlow() + fallbackToOriginalFlow(BiometricFlowType.CREATE) return } - runBiometricFlow( + runBiometricFlowForCreate( biometricEntry = biometricEntry, context = LocalContext.current, openMoreOptionsPage = onMoreOptionSelected, sendDataToProvider = onBiometricEntrySelected, onCancelFlowAndFinish = onCancelFlowAndFinish, + getBiometricPromptState = getBiometricPromptState, + onBiometricPromptStateChange = onBiometricPromptStateChange, createRequestDisplayInfo = requestDisplayInfo, createProviderInfo = enabledProviderInfo, onBiometricFailureFallback = fallbackToOriginalFlow, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 1d262ba5261a..ddd4139b65b6 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -78,13 +78,8 @@ internal fun getCreateEntry( */ internal fun isBiometricFlow( activeEntry: ActiveEntry, - sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>, - requestDisplayInfo: RequestDisplayInfo, -) = findBiometricFlowEntry(activeEntry, isFlowAutoSelectable( - requestDisplayInfo = requestDisplayInfo, - activeEntry = activeEntry, - sortedCreateOptionsPairs = sortedCreateOptionsPairs -)) != null + isAutoSelectFlow: Boolean, +) = findBiometricFlowEntry(activeEntry, isAutoSelectFlow) != null /** * This utility presents the correct resource string for the create flows title conditionally. diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 6d1a3dd98210..72b7814a791a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -52,9 +52,11 @@ import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel import com.android.credentialmanager.R +import com.android.credentialmanager.common.BiometricFlowType +import com.android.credentialmanager.common.BiometricPromptState import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.common.material.ModalBottomSheetDefaults -import com.android.credentialmanager.common.runBiometricFlow +import com.android.credentialmanager.common.runBiometricFlowForGet import com.android.credentialmanager.common.ui.ActionButton import com.android.credentialmanager.common.ui.ActionEntry import com.android.credentialmanager.common.ui.ConfirmButton @@ -154,7 +156,11 @@ fun GetCredentialScreen( onBiometricEntrySelected = viewModel::getFlowOnEntrySelected, fallbackToOriginalFlow = - viewModel::getFlowOnBackToPrimarySelectionScreen, + viewModel::fallbackFromBiometricToNormalFlow, + getBiometricPromptState = + viewModel::getBiometricPromptState, + onBiometricPromptStateChange = + viewModel::onBiometricPromptStateChange ) } else { AllSignInOptionCard( @@ -218,19 +224,23 @@ internal fun BiometricSelectionPage( providerInfoList: List<ProviderInfo>, providerDisplayInfo: ProviderDisplayInfo, onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult?) -> Unit, - fallbackToOriginalFlow: () -> Unit, + fallbackToOriginalFlow: (BiometricFlowType) -> Unit, + getBiometricPromptState: () -> BiometricPromptState, + onBiometricPromptStateChange: (BiometricPromptState) -> Unit, ) { if (biometricEntry == null) { - fallbackToOriginalFlow() + fallbackToOriginalFlow(BiometricFlowType.GET) return } - runBiometricFlow( + runBiometricFlowForGet( biometricEntry = biometricEntry, context = LocalContext.current, openMoreOptionsPage = onMoreOptionSelected, sendDataToProvider = onBiometricEntrySelected, onCancelFlowAndFinish = onCancelFlowAndFinish, onIllegalStateAndFinish = onIllegalStateAndFinish, + getBiometricPromptState = getBiometricPromptState, + onBiometricPromptStateChange = onBiometricPromptStateChange, getRequestDisplayInfo = requestDisplayInfo, getProviderInfoList = providerInfoList, getProviderDisplayInfo = providerDisplayInfo, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index ac776af4f627..b03407b9ebea 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -286,7 +286,7 @@ private fun toGetScreenState( GetScreenState.REMOTE_ONLY else if (isRequestForAllOptions) GetScreenState.ALL_SIGN_IN_OPTIONS - else if (isBiometricFlow(providerDisplayInfo)) + else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo))) GetScreenState.BIOMETRIC_SELECTION else GetScreenState.PRIMARY_SELECTION } @@ -294,9 +294,14 @@ private fun toGetScreenState( /** * Determines if the flow is a biometric flow by taking into account autoselect criteria. */ -internal fun isBiometricFlow(providerDisplayInfo: ProviderDisplayInfo) = - findBiometricFlowEntry(providerDisplayInfo, - findAutoSelectEntry(providerDisplayInfo) != null) != null +internal fun isBiometricFlow(providerDisplayInfo: ProviderDisplayInfo, isAutoSelectFlow: Boolean) = + findBiometricFlowEntry(providerDisplayInfo, isAutoSelectFlow) != null + +/** + * Determines if the flow is an autoselect flow. + */ +internal fun isFlowAutoSelectable(providerDisplayInfo: ProviderDisplayInfo) = + findAutoSelectEntry(providerDisplayInfo) != null internal class CredentialEntryInfoComparatorByTypeThenTimestamp( val typePriorityMap: Map<String, Int>, diff --git a/packages/InputDevices/res/raw/keyboard_layout_thai_kedmanee.kcm b/packages/InputDevices/res/raw/keyboard_layout_thai_kedmanee.kcm new file mode 100644 index 000000000000..2283032e9450 --- /dev/null +++ b/packages/InputDevices/res/raw/keyboard_layout_thai_kedmanee.kcm @@ -0,0 +1,321 @@ +# Copyright 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Thai Kedmanee keyboard layout. +# + +type OVERLAY + +map key 86 PLUS + +### ROW 1 + +key GRAVE { + label: '_' + base: '_' + shift, capslock: '%' +} + +key 1 { + label: '\u0e45' + base: '\u0e45' + shift, capslock: '+' +} + +key 2 { + label: '/' + base: '/' + shift, capslock: '\u0e51' +} + +key 3 { + label: '-' + base: '-' + shift, capslock: '\u0e52' +} + +key 4 { + label: '\u0e20' + base: '\u0e20' + shift, capslock: '\u0e53' +} + +key 5 { + label: '\u0e16' + base: '\u0e16' + shift, capslock: '\u0e54' +} + +key 6 { + label: '\u0e38' + base: '\u0e38' + shift, capslock: '\u0e39' +} + +key 7 { + label: '\u0e36' + base: '\u0e36' + shift, capslock: '\u0e3f' +} + +key 8 { + label: '\u0e04' + base: '\u0e04' + shift, capslock: '\u0e55' +} + +key 9 { + label: '\u0e15' + base: '\u0e15' + shift, capslock: '\u0e56' +} + +key 0 { + label: '\u0e08' + base: '\u0e08' + shift, capslock: '\u0e57' +} + +key MINUS { + label: '\u0e02' + base: '\u0e02' + shift, capslock: '\u0e58' +} + +key EQUALS { + label: '\u0e0a' + base: '\u0e0a' + shift, capslock: '\u0e59' +} + +### ROW 2 + +key Q { + label: '\u0e46' + base: '\u0e46' + shift, capslock: '\u0e50' +} + +key W { + label: '\u0e44' + base: '\u0e44' + shift, capslock: '\u0022' +} + +key E { + label: '\u0e33' + base: '\u0e33' + shift, capslock: '\u0e0e' +} + +key R { + label: '\u0e1e' + base: '\u0e1e' + shift, capslock: '\u0e11' +} + +key T { + label: '\u0e30' + base: '\u0e30' + shift, capslock: '\u0e18' +} + +key Y { + label: '\u0e31' + base: '\u0e31' + shift, capslock: '\u0e4d' +} + +key U { + label: '\u0e35' + base: '\u0e35' + shift, capslock: '\u0e4a' +} + +key I { + label: '\u0e23' + base: '\u0e23' + shift, capslock: '\u0e13' +} + +key O { + label: '\u0e19' + base: '\u0e19' + shift, capslock: '\u0e2f' +} + +key P { + label: '\u0e22' + base: '\u0e22' + shift, capslock: '\u0e0d' +} + +key LEFT_BRACKET { + label: '\u0e1a' + base: '\u0e1a' + shift, capslock: '\u0e10' + ctrl: '%' +} + +key RIGHT_BRACKET { + label: '\u0e25' + base: '\u0e25' + shift, capslock: ',' + ctrl: '\u0e51' +} + +### ROW 3 + +key A { + label: '\u0e1f' + base: '\u0e1f' + shift, capslock: '\u0e24' +} + +key S { + label: '\u0e2b' + base: '\u0e2b' + shift, capslock: '\u0e06' +} + +key D { + label: '\u0e01' + base: '\u0e01' + shift, capslock: '\u0e0f' +} + +key F { + label: '\u0e14' + base: '\u0e14' + shift, capslock: '\u0e42' +} + +key G { + label: '\u0e40' + base: '\u0e40' + shift, capslock: '\u0e0c' +} + +key H { + label: '\u0e49' + base: '\u0e49' + shift, capslock: '\u0e47' +} + +key J { + label: '\u0e48' + base: '\u0e48' + shift, capslock: '\u0e4b' +} + +key K { + label: '\u0e32' + base: '\u0e32' + shift, capslock: '\u0e29' +} + +key L { + label: '\u0e2a' + base: '\u0e2a' + shift, capslock: '\u0e28' +} + +key SEMICOLON { + label: '\u0e27' + base: '\u0e27' + shift, capslock: '\u0e0b' +} + +key APOSTROPHE { + label: '\u0e07' + base: '\u0e07' + shift, capslock: '.' +} + +key BACKSLASH { + label: '\u0e03' + base: '\u0e03' + shift, capslock: '\u0e05' + ctrl: '+' +} + +### ROW 4 + +key PLUS { + label: '\u0e03' + base: '\u0e03' + shift, capslock: '\u0e05' + ctrl: '\u0e52' +} + +key Z { + label: '\u0e1c' + base: '\u0e1c' + shift, capslock: '(' +} + +key X { + label: '\u0e1b' + base: '\u0e1b' + shift, capslock: ')' +} + +key C { + label: '\u0e41' + base: '\u0e41' + shift, capslock: '\u0e09' +} + +key V { + label: '\u0e2d' + base: '\u0e2d' + shift, capslock: '\u0e2e' +} + +key B { + label: '\u0e34' + base: '\u0e34' + shift, capslock: '\u0e3a' +} + +key N { + label: '\u0e37' + base: '\u0e37' + shift, capslock: '\u0e4c' +} + +key M { + label: '\u0e17' + base: '\u0e17' + shift, capslock: '?' +} + +key COMMA { + label: '\u0e21' + base: '\u0e21' + shift, capslock: '\u0e12' +} + +key PERIOD { + label: '\u0e43' + base: '\u0e43' + shift, capslock: '\u0e2c' +} + +key SLASH { + label: '\u0e1d' + base: '\u0e1d' + shift, capslock: '\u0e26' +}
\ No newline at end of file diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml index 1e1394096926..33a1d760f417 100644 --- a/packages/InputDevices/res/values/strings.xml +++ b/packages/InputDevices/res/values/strings.xml @@ -146,4 +146,7 @@ <!-- Georgian keyboard layout label. [CHAR LIMIT=35] --> <string name="keyboard_layout_georgian">Georgian</string> + + <!-- Thai (Kedmanee variant) keyboard layout label. [CHAR LIMIT=35] --> + <string name="keyboard_layout_thai_kedmanee">Thai (Kedmanee)</string> </resources> diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml index ee49b23b3860..4b7ea90eeb38 100644 --- a/packages/InputDevices/res/xml/keyboard_layouts.xml +++ b/packages/InputDevices/res/xml/keyboard_layouts.xml @@ -318,4 +318,11 @@ android:label="@string/keyboard_layout_georgian" android:keyboardLayout="@raw/keyboard_layout_georgian" android:keyboardLocale="ka-Geor" /> + + <keyboard-layout + android:name="keyboard_layout_thai_kedmanee" + android:label="@string/keyboard_layout_thai_kedmanee" + android:keyboardLayout="@raw/keyboard_layout_thai_kedmanee" + android:keyboardLocale="th-Thai" + android:keyboardLayoutType="extended" /> </keyboard-layouts> 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 761bb7918afd..91bd7916b0ab 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 @@ -40,7 +40,7 @@ import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider -import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider +import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider @@ -48,10 +48,12 @@ import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider 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.PagerMainPageProvider 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.scaffold.ScrollablePagerPageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver @@ -84,7 +86,9 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { ArgumentPageProvider, SliderPageProvider, SpinnerPageProvider, - SettingsPagerPageProvider, + PagerMainPageProvider, + NonScrollablePagerPageProvider, + ScrollablePagerPageProvider, FooterPageProvider, IllustrationPageProvider, CategoryPageProvider, 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 1f028d5e7bc9..654719d906a9 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 @@ -39,9 +39,9 @@ import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider -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.PagerMainPageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider @@ -63,7 +63,7 @@ object HomePageProvider : SettingsPageProvider { SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), - SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + PagerMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/NonScrollablePagerPageProvider.kt index dc45e6da0bc1..029773fdf8df 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/NonScrollablePagerPageProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spa.gallery.page +package com.android.settingslib.spa.gallery.scaffold import android.os.Bundle import androidx.compose.foundation.layout.Box @@ -33,24 +33,19 @@ import com.android.settingslib.spa.widget.scaffold.SettingsPager import com.android.settingslib.spa.widget.scaffold.SettingsScaffold import com.android.settingslib.spa.widget.ui.PlaceholderTitle -private const val TITLE = "Sample SettingsPager" +object NonScrollablePagerPageProvider : SettingsPageProvider { + override val name = "NonScrollablePager" + private const val TITLE = "Sample Non Scrollable SettingsPager" -object SettingsPagerPageProvider : SettingsPageProvider { - override val name = "SettingsPager" - - fun buildInjectEntry(): SettingsEntryBuilder { - return SettingsEntryBuilder.createInject(owner = createSettingsPage()) - .setUiLayoutFn { - Preference(object : PreferenceModel { - override val title = TITLE - override val onClick = navigator(name) - }) - } - } + fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = createSettingsPage()) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } - override fun getTitle(arguments: Bundle?): String { - return TITLE - } + override fun getTitle(arguments: Bundle?) = TITLE @Composable override fun Page(arguments: Bundle?) { @@ -66,8 +61,8 @@ object SettingsPagerPageProvider : SettingsPageProvider { @Preview(showBackground = true) @Composable -private fun SettingsPagerPagePreview() { +private fun NonScrollablePagerPageProviderPreview() { SettingsTheme { - SettingsPagerPageProvider.Page(null) + NonScrollablePagerPageProvider.Page(null) } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt new file mode 100644 index 000000000000..66cc38f74b07 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery.scaffold + +import android.os.Bundle +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.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel + +object PagerMainPageProvider : SettingsPageProvider { + override val name = "PagerMain" + private val owner = createSettingsPage() + private const val TITLE = "Category: Pager" + + override fun buildEntry(arguments: Bundle?) = listOf( + NonScrollablePagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ScrollablePagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ) + + fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + + override fun getTitle(arguments: Bundle?) = TITLE +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/ScrollablePagerPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/ScrollablePagerPageProvider.kt new file mode 100644 index 000000000000..689a98a16b23 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/ScrollablePagerPageProvider.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery.scaffold + +import android.os.Bundle +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +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.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.SettingsPager +import com.android.settingslib.spa.widget.scaffold.SettingsScaffold + +object ScrollablePagerPageProvider : SettingsPageProvider { + override val name = "ScrollablePager" + private const val TITLE = "Sample Scrollable SettingsPager" + + fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = createSettingsPage()) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + + override fun getTitle(arguments: Bundle?) = TITLE + + @Composable + override fun Page(arguments: Bundle?) { + SettingsScaffold(title = getTitle(arguments)) { paddingValues -> + Box(Modifier.padding(paddingValues)) { + SettingsPager(listOf("Personal", "Work")) { + LazyColumn { + items(30) { + Preference(object : PreferenceModel { + override val title = it.toString() + }) + } + } + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun ScrollablePagerPageProviderPreview() { + SettingsTheme { + ScrollablePagerPageProvider.Page(null) + } +} 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 index 6fc8de3ac49c..a0ab2ce6945d 100644 --- 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 @@ -22,6 +22,10 @@ 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.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider @@ -37,6 +41,8 @@ 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 +import com.android.settingslib.spa.widget.ui.Spinner +import com.android.settingslib.spa.widget.ui.SpinnerOption private const val TITLE = "Sample SuwScaffold" @@ -67,13 +73,12 @@ private fun Page() { 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 - }) + var selectedId by rememberSaveable { mutableIntStateOf(1) } + Spinner( + options = (1..3).map { SpinnerOption(id = it, text = "Option $it") }, + selectedId = selectedId, + setId = { selectedId = it }, + ) Column(Modifier.padding(SettingsDimension.itemPadding)) { SettingsBody("To add another SIM, download a new eSIM.") } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt index 791893b3c056..ef1a137198c2 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt @@ -37,6 +37,7 @@ fun TwoTargetSwitchPreference( SettingsSwitch( checked = model.checked(), changeable = model.changeable, + contentDescription = model.title, onCheckedChange = model.onCheckedChange, ) } 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 index f372a45f9e59..163766a7a9c5 100644 --- 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 @@ -19,7 +19,6 @@ 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 @@ -33,6 +32,8 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.movableContentOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import com.android.settingslib.spa.framework.theme.SettingsDimension @@ -50,7 +51,7 @@ fun SuwScaffold( title: String, actionButton: BottomAppBarButton? = null, dismissButton: BottomAppBarButton? = null, - content: @Composable ColumnScope.() -> Unit, + content: @Composable () -> Unit, ) { ActivityTitle(title) Scaffold { innerPadding -> @@ -59,6 +60,7 @@ fun SuwScaffold( .padding(innerPadding) .padding(top = SettingsDimension.itemPaddingAround) ) { + val movableContent = remember(content) { movableContentOf { content() } } // Use single column layout in portrait, two columns in landscape. val useSingleColumn = maxWidth < maxHeight if (useSingleColumn) { @@ -69,7 +71,7 @@ fun SuwScaffold( .verticalScroll(rememberScrollState()) ) { Header(imageVector, title) - content() + movableContent() } BottomBar(actionButton, dismissButton) } @@ -82,8 +84,9 @@ fun SuwScaffold( Column( Modifier .weight(1f) - .verticalScroll(rememberScrollState())) { - content() + .verticalScroll(rememberScrollState()) + ) { + movableContent() } } BottomBar(actionButton, dismissButton) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt index a0da2418acbc..5155406b6d79 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt @@ -20,12 +20,16 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog @Composable internal fun SettingsSwitch( checked: Boolean?, changeable: () -> Boolean, + contentDescription: String? = null, onCheckedChange: ((newChecked: Boolean) -> Unit)? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { @@ -33,6 +37,9 @@ internal fun SettingsSwitch( Switch( checked = checked, onCheckedChange = wrapOnSwitchWithLog(onCheckedChange), + modifier = if (contentDescription != null) Modifier.semantics { + this.contentDescription = contentDescription + } else Modifier, enabled = changeable(), interactionSource = interactionSource, ) diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java index a4089c0d8697..7f4bebcf4a62 100644 --- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java @@ -52,16 +52,12 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; -/** - * Description of a single dashboard tile that the user can select. - */ +/** Description of a single dashboard tile that the user can select. */ public abstract class Tile implements Parcelable { private static final String TAG = "Tile"; - /** - * Optional list of user handles which the intent should be launched on. - */ + /** Optional list of user handles which the intent should be launched on. */ public ArrayList<UserHandle> userHandle = new ArrayList<>(); public HashMap<UserHandle, PendingIntent> pendingIntentMap = new HashMap<>(); @@ -76,6 +72,7 @@ public abstract class Tile implements Parcelable { private CharSequence mSummaryOverride; private Bundle mMetaData; private String mCategory; + private String mGroupKey; public Tile(ComponentInfo info, String category, Bundle metaData) { mComponentInfo = info; @@ -83,6 +80,9 @@ public abstract class Tile implements Parcelable { mComponentName = mComponentInfo.name; mCategory = category; mMetaData = metaData; + if (mMetaData != null) { + mGroupKey = metaData.getString(META_DATA_PREFERENCE_GROUP_KEY); + } mIntent = new Intent().setClassName(mComponentPackage, mComponentName); if (isNewTask()) { mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -102,6 +102,7 @@ public abstract class Tile implements Parcelable { if (isNewTask()) { mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } + mGroupKey = in.readString(); } @Override @@ -121,16 +122,13 @@ public abstract class Tile implements Parcelable { } dest.writeString(mCategory); dest.writeBundle(mMetaData); + dest.writeString(mGroupKey); } - /** - * Unique ID of the tile - */ + /** Unique ID of the tile */ public abstract int getId(); - /** - * Human-readable description of the tile - */ + /** Human-readable description of the tile */ public abstract String getDescription(); protected abstract ComponentInfo getComponentInfo(Context context); @@ -147,16 +145,12 @@ public abstract class Tile implements Parcelable { return mComponentName; } - /** - * Intent to launch when the preference is selected. - */ + /** Intent to launch when the preference is selected. */ public Intent getIntent() { return mIntent; } - /** - * Category in which the tile should be placed. - */ + /** Category in which the tile should be placed. */ public String getCategory() { return mCategory; } @@ -165,9 +159,7 @@ public abstract class Tile implements Parcelable { mCategory = newCategoryKey; } - /** - * Priority of this tile, used for display ordering. - */ + /** Priority of this tile, used for display ordering. */ public int getOrder() { if (hasOrder()) { return mMetaData.getInt(META_DATA_KEY_ORDER); @@ -176,32 +168,24 @@ public abstract class Tile implements Parcelable { } } - /** - * Check whether tile has order. - */ + /** Check whether tile has order. */ public boolean hasOrder() { return mMetaData != null && mMetaData.containsKey(META_DATA_KEY_ORDER) && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer; } - /** - * Check whether tile has a switch. - */ + /** Check whether tile has a switch. */ public boolean hasSwitch() { return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_SWITCH_URI); } - /** - * Check whether tile has a pending intent. - */ + /** Check whether tile has a pending intent. */ public boolean hasPendingIntent() { return !pendingIntentMap.isEmpty(); } - /** - * Title of the tile that is shown to the user. - */ + /** Title of the tile that is shown to the user. */ public CharSequence getTitle(Context context) { CharSequence title = null; ensureMetadataNotStale(context); @@ -238,9 +222,7 @@ public abstract class Tile implements Parcelable { mSummaryOverride = summaryOverride; } - /** - * Optional summary describing what this tile controls. - */ + /** Optional summary describing what this tile controls. */ public CharSequence getSummary(Context context) { if (mSummaryOverride != null) { return mSummaryOverride; @@ -275,16 +257,12 @@ public abstract class Tile implements Parcelable { mMetaData = metaData; } - /** - * The metaData from the activity that defines this tile. - */ + /** The metaData from the activity that defines this tile. */ public Bundle getMetaData() { return mMetaData; } - /** - * Optional key to use for this tile. - */ + /** Optional key to use for this tile. */ public String getKey(Context context) { ensureMetadataNotStale(context); if (!hasKey()) { @@ -297,9 +275,7 @@ public abstract class Tile implements Parcelable { } } - /** - * Check whether title has key. - */ + /** Check whether title has key. */ public boolean hasKey() { return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT); } @@ -325,8 +301,9 @@ public abstract class Tile implements Parcelable { if (iconResId != 0 && iconResId != android.R.color.transparent) { final Icon icon = Icon.createWithResource(componentInfo.packageName, iconResId); if (isIconTintable(context)) { - final TypedArray a = context.obtainStyledAttributes(new int[]{ - android.R.attr.colorControlNormal}); + final TypedArray a = + context.obtainStyledAttributes( + new int[] {android.R.attr.colorControlNormal}); final int tintColor = a.getColor(0, 0); a.recycle(); icon.setTint(tintColor); @@ -349,26 +326,22 @@ public abstract class Tile implements Parcelable { return false; } - /** - * Whether the {@link Activity} should be launched in a separate task. - */ + /** Whether the {@link Activity} should be launched in a separate task. */ public boolean isNewTask() { - if (mMetaData != null - && mMetaData.containsKey(META_DATA_NEW_TASK)) { + if (mMetaData != null && mMetaData.containsKey(META_DATA_NEW_TASK)) { return mMetaData.getBoolean(META_DATA_NEW_TASK); } return false; } - /** - * Ensures metadata is not stale for this tile. - */ + /** Ensures metadata is not stale for this tile. */ private void ensureMetadataNotStale(Context context) { final PackageManager pm = context.getApplicationContext().getPackageManager(); try { - final long lastUpdateTime = pm.getPackageInfo(mComponentPackage, - PackageManager.GET_META_DATA).lastUpdateTime; + final long lastUpdateTime = + pm.getPackageInfo(mComponentPackage, PackageManager.GET_META_DATA) + .lastUpdateTime; if (lastUpdateTime == mLastUpdateTime) { // All good. Do nothing return; @@ -382,72 +355,60 @@ public abstract class Tile implements Parcelable { } } - public static final Creator<Tile> CREATOR = new Creator<Tile>() { - public Tile createFromParcel(Parcel source) { - final boolean isProviderTile = source.readBoolean(); - // reset the Parcel pointer before delegating to the real constructor. - source.setDataPosition(0); - return isProviderTile ? new ProviderTile(source) : new ActivityTile(source); - } + public static final Creator<Tile> CREATOR = + new Creator<Tile>() { + public Tile createFromParcel(Parcel source) { + final boolean isProviderTile = source.readBoolean(); + // reset the Parcel pointer before delegating to the real constructor. + source.setDataPosition(0); + return isProviderTile ? new ProviderTile(source) : new ActivityTile(source); + } - public Tile[] newArray(int size) { - return new Tile[size]; - } - }; + public Tile[] newArray(int size) { + return new Tile[size]; + } + }; - /** - * Check whether tile only has primary profile. - */ + /** Check whether tile only has primary profile. */ public boolean isPrimaryProfileOnly() { return isPrimaryProfileOnly(mMetaData); } static boolean isPrimaryProfileOnly(Bundle metaData) { - String profile = metaData != null - ? metaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL; + String profile = metaData != null ? metaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL; profile = (profile != null ? profile : PROFILE_ALL); return TextUtils.equals(profile, PROFILE_PRIMARY); } - /** - * Returns whether the tile belongs to another group / category. - */ + /** Returns whether the tile belongs to another group / category. */ public boolean hasGroupKey() { - return mMetaData != null - && !TextUtils.isEmpty(mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY)); + return !TextUtils.isEmpty(mGroupKey); } - /** - * Returns the group / category key this tile belongs to. - */ + /** Set the group / PreferenceCategory key this tile belongs to. */ + public void setGroupKey(String groupKey) { + mGroupKey = groupKey; + } + + /** Returns the group / category key this tile belongs to. */ public String getGroupKey() { - return (mMetaData == null) ? null : mMetaData.getString(META_DATA_PREFERENCE_GROUP_KEY); + return mGroupKey; } - /** - * Returns if this is searchable. - */ + /** Returns if this is searchable. */ public boolean isSearchable() { return mMetaData == null || mMetaData.getBoolean(META_DATA_PREFERENCE_SEARCHABLE, true); } - /** - * The type of the tile. - */ + /** The type of the tile. */ public enum Type { - /** - * A preference that can be tapped on to open a new page. - */ + /** A preference that can be tapped on to open a new page. */ ACTION, - /** - * A preference that can be tapped on to open an external app. - */ + /** A preference that can be tapped on to open an external app. */ EXTERNAL_ACTION, - /** - * A preference that shows an on / off switch that can be toggled by the user. - */ + /** A preference that shows an on / off switch that can be toggled by the user. */ SWITCH, /** @@ -460,7 +421,7 @@ public abstract class Tile implements Parcelable { * A preference category with a title that can be used to group multiple preferences * together. */ - GROUP; + GROUP } /** diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java index d0929e19bc9b..b949cd58a862 100644 --- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java @@ -77,9 +77,7 @@ public class TileUtils { */ public static final String IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS"; - /** - * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities. - */ + /** Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities. */ private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS"; private static final String OPERATOR_SETTINGS = @@ -101,9 +99,7 @@ public class TileUtils { */ static final String EXTRA_CATEGORY_KEY = "com.android.settings.category"; - /** - * The key used to get the package name of the icon resource for the preference. - */ + /** The key used to get the package name of the icon resource for the preference. */ static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package"; /** @@ -145,18 +141,17 @@ public class TileUtils { "com.android.settings.bg.argb"; /** - * Name of the meta-data item that should be set in the AndroidManifest.xml - * to specify the content provider providing the icon that should be displayed for - * the preference. + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the + * content provider providing the icon that should be displayed for the preference. * - * Icon provided by the content provider overrides any static icon. + * <p>Icon provided by the content provider overrides any static icon. */ public static final String META_DATA_PREFERENCE_ICON_URI = "com.android.settings.icon_uri"; /** - * Name of the meta-data item that should be set in the AndroidManifest.xml - * to specify whether the icon is tintable. This should be a boolean value {@code true} or - * {@code false}, set using {@code android:value} + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify whether + * the icon is tintable. This should be a boolean value {@code true} or {@code false}, set using + * {@code android:value} */ public static final String META_DATA_PREFERENCE_ICON_TINTABLE = "com.android.settings.icon_tintable"; @@ -171,41 +166,36 @@ public class TileUtils { public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title"; /** - * Name of the meta-data item that should be set in the AndroidManifest.xml - * to specify the content provider providing the title text that should be displayed for the - * preference. + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the + * content provider providing the title text that should be displayed for the preference. * - * Title provided by the content provider overrides any static title. + * <p>Title provided by the content provider overrides any static title. */ - public static final String META_DATA_PREFERENCE_TITLE_URI = - "com.android.settings.title_uri"; + public static final String META_DATA_PREFERENCE_TITLE_URI = "com.android.settings.title_uri"; /** - * Name of the meta-data item that should be set in the AndroidManifest.xml - * to specify the summary text that should be displayed for the preference. + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the + * summary text that should be displayed for the preference. */ public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary"; /** - * Name of the meta-data item that should be set in the AndroidManifest.xml - * to specify the content provider providing the summary text that should be displayed for the - * preference. + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the + * content provider providing the summary text that should be displayed for the preference. * - * Summary provided by the content provider overrides any static summary. + * <p>Summary provided by the content provider overrides any static summary. */ public static final String META_DATA_PREFERENCE_SUMMARY_URI = "com.android.settings.summary_uri"; /** - * Name of the meta-data item that should be set in the AndroidManifest.xml - * to specify the content provider providing the switch that should be displayed for the - * preference. + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the + * content provider providing the switch that should be displayed for the preference. * - * This works with {@link #META_DATA_PREFERENCE_KEYHINT} which should also be set in the + * <p>This works with {@link #META_DATA_PREFERENCE_KEYHINT} which should also be set in the * AndroidManifest.xml */ - public static final String META_DATA_PREFERENCE_SWITCH_URI = - "com.android.settings.switch_uri"; + public static final String META_DATA_PREFERENCE_SWITCH_URI = "com.android.settings.switch_uri"; /** * Name of the meta-data item that can be set from the content provider providing the intent @@ -215,8 +205,8 @@ public class TileUtils { "com.android.settings.pending_intent"; /** - * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile, - * the app will always be run in the primary profile. + * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile, the app will + * always be run in the primary profile. * * @see #META_DATA_KEY_PROFILE */ @@ -231,11 +221,11 @@ public class TileUtils { public static final String PROFILE_ALL = "all_profiles"; /** - * Name of the meta-data item that should be set in the AndroidManifest.xml - * to specify the profile in which the app should be run when the device has a managed profile. - * The default value is {@link #PROFILE_ALL} which means the user will be presented with a - * dialog to choose the profile. If set to {@link #PROFILE_PRIMARY} the app will always be - * run in the primary profile. + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify the + * profile in which the app should be run when the device has a managed profile. The default + * value is {@link #PROFILE_ALL} which means the user will be presented with a dialog to choose + * the profile. If set to {@link #PROFILE_PRIMARY} the app will always be run in the primary + * profile. * * @see #PROFILE_PRIMARY * @see #PROFILE_ALL @@ -243,20 +233,16 @@ public class TileUtils { public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile"; /** - * Name of the meta-data item that should be set in the AndroidManifest.xml - * to specify whether the {@link android.app.Activity} should be launched in a separate task. - * This should be a boolean value {@code true} or {@code false}, set using {@code android:value} + * Name of the meta-data item that should be set in the AndroidManifest.xml to specify whether + * the {@link android.app.Activity} should be launched in a separate task. This should be a + * boolean value {@code true} or {@code false}, set using {@code android:value} */ public static final String META_DATA_NEW_TASK = "com.android.settings.new_task"; - /** - * If the entry should be shown in settings search results. Defaults to true. - */ + /** If the entry should be shown in settings search results. Defaults to true. */ public static final String META_DATA_PREFERENCE_SEARCHABLE = "com.android.settings.searchable"; - /** - * Build a list of DashboardCategory. - */ + /** Build a list of DashboardCategory. */ public static List<DashboardCategory> getCategories(Context context, Map<Pair<String, String>, Tile> cache) { final long startTime = System.currentTimeMillis(); @@ -341,8 +327,8 @@ public class TileUtils { UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent) { final PackageManager pm = context.getPackageManager(); - final List<ResolveInfo> results = pm.queryIntentContentProvidersAsUser(intent, - 0 /* flags */, user.getIdentifier()); + final List<ResolveInfo> results = + pm.queryIntentContentProvidersAsUser(intent, 0 /* flags */, user.getIdentifier()); for (ResolveInfo resolved : results) { if (!resolved.system) { // Do not allow any app to add to settings, only system ones. @@ -403,6 +389,8 @@ public class TileUtils { tile.setMetaData(metaData); } + tile.setGroupKey(metaData.getString(META_DATA_PREFERENCE_GROUP_KEY)); + if (!tile.userHandle.contains(user)) { tile.userHandle.add(user); } @@ -448,15 +436,15 @@ public class TileUtils { /** * Returns the complete uri from the meta data key of the tile. * - * A complete uri should contain at least one path segment and be one of the following types: - * content://authority/method - * content://authority/method/key + * <p>A complete uri should contain at least one path segment and be one of the following types: + * <br>content://authority/method + * <br>content://authority/method/key * - * If the uri from the tile is not complete, build a uri by the default method and the + * <p>If the uri from the tile is not complete, build a uri by the default method and the * preference key. * - * @param tile Tile which contains meta data - * @param metaDataKey Key mapping to the uri in meta data + * @param tile Tile which contains meta data + * @param metaDataKey Key mapping to the uri in meta data * @param defaultMethod Method to be attached to the uri by default if it has no path segment * @return Uri associated with the key */ @@ -501,9 +489,9 @@ public class TileUtils { /** * Gets the icon package name and resource id from content provider. * - * @param context context + * @param context context * @param packageName package name of the target activity - * @param uri URI for the content provider + * @param uri URI for the content provider * @param providerMap Maps URI authorities to providers * @return package name and resource id of the icon specified */ @@ -532,10 +520,10 @@ public class TileUtils { /** * Gets text associated with the input key from the content provider. * - * @param context context - * @param uri URI for the content provider + * @param context context + * @param uri URI for the content provider * @param providerMap Maps URI authorities to providers - * @param key Key mapping to the text in bundle returned by the content provider + * @param key Key mapping to the text in bundle returned by the content provider * @return Text associated with the key, if returned by the content provider */ public static String getTextFromUri(Context context, Uri uri, @@ -547,10 +535,10 @@ public class TileUtils { /** * Gets boolean associated with the input key from the content provider. * - * @param context context - * @param uri URI for the content provider + * @param context context + * @param uri URI for the content provider * @param providerMap Maps URI authorities to providers - * @param key Key mapping to the text in bundle returned by the content provider + * @param key Key mapping to the text in bundle returned by the content provider * @return Boolean associated with the key, if returned by the content provider */ public static boolean getBooleanFromUri(Context context, Uri uri, @@ -562,11 +550,11 @@ public class TileUtils { /** * Puts boolean associated with the input key to the content provider. * - * @param context context - * @param uri URI for the content provider + * @param context context + * @param uri URI for the content provider * @param providerMap Maps URI authorities to providers - * @param key Key mapping to the text in bundle returned by the content provider - * @param value Boolean associated with the key + * @param key Key mapping to the text in bundle returned by the content provider + * @param value Boolean associated with the key * @return Bundle associated with the action, if returned by the content provider */ public static Bundle putBooleanToUriAndGetResult(Context context, Uri uri, diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 9ec5caa2fd92..89f54d9b3b3b 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -52,3 +52,13 @@ flag { description: "Hide exclusively managed Bluetooth devices in BT settings menu." bug: "324475542" } + +flag { + name: "enable_set_preferred_transport_for_le_audio_device" + namespace: "bluetooth" + description: "Enable setting preferred transport for Le Audio device" + bug: "330581926" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml index 89d6ac39598b..3ed17247a5e3 100644 --- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml +++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml @@ -53,7 +53,7 @@ <EditText android:id="@+id/user_name" android:layout_width="match_parent" - android:layout_height="@dimen/user_name_height_in_user_info_dialog" + android:layout_height="wrap_content" android:layout_gravity="center" android:minWidth="200dp" android:layout_marginStart="6dp" diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 2bd4d023cd72..ab049042b5f9 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -82,7 +82,6 @@ <dimen name="add_a_photo_circled_padding">6dp</dimen> <dimen name="user_photo_size_in_user_info_dialog">112dp</dimen> <dimen name="add_a_photo_icon_size_in_user_info_dialog">32dp</dimen> - <dimen name="user_name_height_in_user_info_dialog">48sp</dimen> <!-- Minimum increment between density scales. --> <fraction name="display_density_min_scale_interval">9%</fraction> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 56118dae3f96..06c41cb7cbc7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -1993,6 +1993,40 @@ public class ApplicationsState { }; /** + * Displays a combined list with "downloaded" and "visible in launcher" apps which belong to a + * user which is either not in quiet mode or allows showing apps even when in quiet mode. + */ + public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET = new AppFilter() { + @Override + public void init() { + } + + @Override + public boolean filterApp(@NonNull AppEntry entry) { + if (entry.hideInQuietMode) { + return false; + } + if (AppUtils.isInstant(entry.info)) { + return false; + } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { + return true; + } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) { + return true; + } else if (entry.hasLauncherEntry) { + return true; + } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM) && entry.isHomeApp) { + return true; + } + return false; + } + + @Override + public void refreshAppEntryOnRebuild(@NonNull AppEntry appEntry, boolean hideInQuietMode) { + appEntry.hideInQuietMode = hideInQuietMode; + } + }; + + /** * Displays a combined list with "downloaded" and "visible in launcher" apps only. */ public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT = new AppFilter() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 4777b0de0732..04516eba250e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static com.android.settingslib.flags.Flags.enableSetPreferredTransportForLeAudioDevice; + import android.annotation.CallbackExecutor; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -288,6 +290,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mLocalNapRoleConnected = true; } } + if (enableSetPreferredTransportForLeAudioDevice() + && profile instanceof HidProfile) { + updatePreferredTransport(); + } } else if (profile instanceof MapProfile && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { profile.setEnabled(mDevice, false); @@ -300,12 +306,34 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mLocalNapRoleConnected = false; } + if (enableSetPreferredTransportForLeAudioDevice() + && profile instanceof LeAudioProfile) { + updatePreferredTransport(); + } + HearingAidStatsLogUtils.updateHistoryIfNeeded(mContext, this, profile, newProfileState); } fetchActiveDevices(); } + private void updatePreferredTransport() { + if (mProfiles.stream().noneMatch(p -> p instanceof LeAudioProfile) + || mProfiles.stream().noneMatch(p -> p instanceof HidProfile)) { + return; + } + // Both LeAudioProfile and HidProfile are connectable. + if (!mProfileManager + .getHidProfile() + .setPreferredTransport( + mDevice, + mProfileManager.getLeAudioProfile().isEnabled(mDevice) + ? BluetoothDevice.TRANSPORT_LE + : BluetoothDevice.TRANSPORT_BREDR)) { + Log.w(TAG, "Fail to set preferred transport"); + } + } + @VisibleForTesting void setProfileConnectedStatus(int profileId, boolean isFailed) { switch (profileId) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index 5b91ac9d3dab..b849d44622b2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -27,6 +27,8 @@ import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; +import androidx.annotation.NonNull; + import com.android.settingslib.R; import java.util.List; @@ -187,6 +189,14 @@ public class HidProfile implements LocalBluetoothProfile { } } + /** Set preferred transport for the device */ + public boolean setPreferredTransport(@NonNull BluetoothDevice device, int transport) { + if (mService != null) { + mService.setPreferredTransport(device, transport); + } + return false; + } + protected void finalize() { Log.d(TAG, "finalize()"); if (mService != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java index 6c12cb7f1512..9df23aa2fe29 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java @@ -42,6 +42,7 @@ import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.Looper; +import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -336,7 +337,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { + ", sourceId = " + sourceId); } - updateFallbackActiveDeviceIfNeeded(); } @Override @@ -468,6 +468,15 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { mServiceBroadcast.startBroadcast(settings); } + /** Checks if the broadcast is playing. */ + public boolean isPlaying(int broadcastId) { + if (mServiceBroadcast == null) { + Log.d(TAG, "check isPlaying failed, the BluetoothLeBroadcast is null."); + return false; + } + return mServiceBroadcast.isPlaying(broadcastId); + } + private BluetoothLeBroadcastSettings buildBroadcastSettings( boolean isPublic, @Nullable String broadcastName, @@ -1025,6 +1034,16 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { /** Update fallback active device if needed. */ public void updateFallbackActiveDeviceIfNeeded() { + if (mServiceBroadcast == null) { + Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null"); + return; + } + List<BluetoothLeBroadcastMetadata> sources = mServiceBroadcast.getAllBroadcastMetadata(); + if (sources.stream() + .noneMatch(source -> mServiceBroadcast.isPlaying(source.getBroadcastId()))) { + Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no broadcast ongoing"); + return; + } if (mServiceBroadcastAssistant == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null"); return; @@ -1117,10 +1136,19 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings."); return; } + if (isWorkProfile(mContext)) { + Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered for work profile."); + return; + } Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE); intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state); intent.setPackage(mContext.getPackageName()); Log.e(TAG, "notifyBroadcastStateChange for state = " + state); mContext.sendBroadcast(intent); } + + private boolean isWorkProfile(Context context) { + UserManager userManager = context.getSystemService(UserManager.class); + return userManager != null && userManager.isManagedProfile(); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 79e4c374667e..4055986e8a57 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -572,8 +572,7 @@ public class LocalBluetoothProfileManager { return mSapProfile; } - @VisibleForTesting - HidProfile getHidProfile() { + public HidProfile getHidProfile() { return mHidProfile; } diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java index b015b2bce60a..46f229035839 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java @@ -184,7 +184,7 @@ public class EditUserInfoController { dialogHelper .setTitle(R.string.user_info_settings_title) .addCustomView(content) - .setPositiveButton(android.R.string.ok, view -> { + .setPositiveButton(R.string.okay, view -> { Drawable newUserIcon = mEditUserPhotoController != null ? mEditUserPhotoController.getNewUserPhotoDrawable() : null; @@ -201,7 +201,7 @@ public class EditUserInfoController { } dialogHelper.getDialog().dismiss(); }) - .setBackButton(android.R.string.cancel, view -> { + .setBackButton(R.string.cancel, view -> { clear(); if (cancelCallback != null) { cancelCallback.run(); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java index b9748883b25d..fef05612a8cb 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java @@ -240,6 +240,56 @@ public class ApplicationsStateTest { } @Test + public void testDownloadAndLauncherNotInQuietAcceptsCorrectApps() { + mEntry.isHomeApp = false; + mEntry.hasLauncherEntry = false; + + // should include updated system apps + when(mEntry.info.isInstantApp()).thenReturn(false); + mEntry.info.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry)) + .isTrue(); + + // should not include system apps other than the home app + mEntry.info.flags = ApplicationInfo.FLAG_SYSTEM; + mEntry.isHomeApp = false; + mEntry.hasLauncherEntry = false; + assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry)) + .isFalse(); + + // should include the home app + mEntry.isHomeApp = true; + assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry)) + .isTrue(); + + // should include any System app with a launcher entry + mEntry.isHomeApp = false; + mEntry.hasLauncherEntry = true; + assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry)) + .isTrue(); + + // should not include updated system apps when in quiet mode + when(mEntry.info.isInstantApp()).thenReturn(false); + mEntry.info.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + mEntry.hideInQuietMode = true; + assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry)) + .isFalse(); + + // should not include the home app when in quiet mode + mEntry.isHomeApp = true; + mEntry.hideInQuietMode = true; + assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry)) + .isFalse(); + + // should not include any System app with a launcher entry when in quiet mode + mEntry.isHomeApp = false; + mEntry.hasLauncherEntry = true; + mEntry.hideInQuietMode = true; + assertThat(ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_NOT_QUIET.filterApp(mEntry)) + .isFalse(); + } + + @Test public void testOtherAppsRejectsLegacyGame() { mEntry.info.flags = ApplicationInfo.FLAG_IS_GAME; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 5996dbb322fc..646e9ebd4f09 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -15,6 +15,8 @@ */ package com.android.settingslib.bluetooth; +import static com.android.settingslib.flags.Flags.FLAG_ENABLE_SET_PREFERRED_TRANSPORT_FOR_LE_AUDIO_DEVICE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -86,6 +88,9 @@ public class CachedBluetoothDeviceTest { private HapClientProfile mHapClientProfile; @Mock private LeAudioProfile mLeAudioProfile; + + @Mock + private HidProfile mHidProfile; @Mock private BluetoothDevice mDevice; @Mock @@ -104,6 +109,7 @@ public class CachedBluetoothDeviceTest { public void setUp() { MockitoAnnotations.initMocks(this); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TV_MEDIA_OUTPUT_DIALOG); + mSetFlagsRule.enableFlags(FLAG_ENABLE_SET_PREFERRED_TRANSPORT_FOR_LE_AUDIO_DEVICE); mContext = RuntimeEnvironment.application; mAudioManager = mContext.getSystemService(AudioManager.class); mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); @@ -118,6 +124,8 @@ public class CachedBluetoothDeviceTest { when(mHearingAidProfile.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID); when(mLeAudioProfile.isProfileReady()).thenReturn(true); when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO); + when(mHidProfile.isProfileReady()).thenReturn(true); + when(mHidProfile.getProfileId()).thenReturn(BluetoothProfile.HID_HOST); mCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mDevice)); mSubCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mSubDevice)); doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel(); @@ -1819,6 +1827,32 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.isConnectedHearingAidDevice()).isFalse(); } + @Test + public void leAudioHidDevice_leAudioEnabled_setPreferredTransportToLE() { + + when(mProfileManager.getHidProfile()).thenReturn(mHidProfile); + when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); + when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(true); + + updateProfileStatus(mHidProfile, BluetoothProfile.STATE_CONNECTED); + updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED); + + verify(mHidProfile).setPreferredTransport(mDevice, BluetoothDevice.TRANSPORT_LE); + } + + @Test + public void leAudioHidDevice_leAudioDisabled_setPreferredTransportToBredr() { + when(mProfileManager.getHidProfile()).thenReturn(mHidProfile); + when(mProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); + when(mLeAudioProfile.isEnabled(mDevice)).thenReturn(false); + + updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED); + updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED); + updateProfileStatus(mHidProfile, BluetoothProfile.STATE_CONNECTED); + + verify(mHidProfile).setPreferredTransport(mDevice, BluetoothDevice.TRANSPORT_BREDR); + } + private HearingAidInfo getLeftAshaHearingAidInfo() { return new HearingAidInfo.Builder() .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT) diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index 5f3d1eafb4fb..0ab99fac9ba3 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -30,8 +30,6 @@ import static com.android.systemui.accessibility.accessibilitymenu.Accessibility import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME; -import static com.google.common.truth.Truth.assertThat; - import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Instrumentation; import android.app.KeyguardManager; @@ -449,7 +447,10 @@ public class AccessibilityMenuServiceTest { closeScreen(); wakeUpScreen(); - assertThat(isMenuVisible()).isFalse(); + TestUtils.waitUntil("Menu did not close.", + TIMEOUT_UI_CHANGE_S, + () -> !isMenuVisible() + ); } @Test diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 2ad98547a19c..fd2fa07f5e9d 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -338,6 +338,16 @@ flag { } flag { + name: "status_bar_monochrome_icons_fix" + namespace: "systemui" + description: "Fixes the status bar icon size when drawing InsetDrawables (ie. monochrome icons)" + bug: "329091967" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "compose_bouncer" namespace: "systemui" description: "Use the new compose bouncer in SystemUI" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt index 4d327e1d8beb..6c982a045084 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt @@ -41,9 +41,9 @@ fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec( maxMarginXdp: Float, maxMarginYdp: Float, minScale: Float, - translateXEasing: Interpolator = Interpolators.STANDARD_DECELERATE, + translateXEasing: Interpolator = Interpolators.BACK_GESTURE, translateYEasing: Interpolator = Interpolators.LINEAR, - scaleEasing: Interpolator = Interpolators.STANDARD_DECELERATE, + scaleEasing: Interpolator = Interpolators.BACK_GESTURE, ): BackAnimationSpec { return BackAnimationSpec { backEvent, progressY, result -> val displayMetrics = displayMetricsProvider() diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt new file mode 100644 index 000000000000..d50979ccd01d --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/PaintDrawCallback.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.surfaceeffects + +import android.graphics.Paint +import android.graphics.RenderEffect + +/** + * A callback with a [Paint] object that contains shader info, which is triggered every frame while + * animation is playing. Note that the [Paint] object here is always the same instance. + * + * This approach is more performant than other ones because [RenderEffect] forces an intermediate + * render pass of the View to a texture to feed into it. + * + * The usage of this callback is as follows: + * <pre>{@code + * private var paint: Paint? = null + * // Override [View.onDraw]. + * override fun onDraw(canvas: Canvas) { + * // RuntimeShader requires hardwareAcceleration. + * if (!canvas.isHardwareAccelerated) return + * + * paint?.let { canvas.drawPaint(it) } + * } + * + * // Given that this is called [PaintDrawCallback.onDraw] + * fun draw(paint: Paint) { + * this.paint = paint + * + * // Must call invalidate to trigger View#onDraw + * invalidate() + * } + * }</pre> + * + * Please refer to [RenderEffectDrawCallback] for alternative approach. + */ +interface PaintDrawCallback { + fun onDraw(paint: Paint) +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt new file mode 100644 index 000000000000..db7ee58090a9 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/RenderEffectDrawCallback.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.surfaceeffects + +import android.graphics.RenderEffect + +/** + * A callback with a [RenderEffect] object that contains shader info, which is triggered every frame + * while animation is playing. Note that the [RenderEffect] instance is different each time to + * update shader uniforms. + * + * The usage of this callback is as follows: + * <pre>{@code + * private val xEffectDrawingCallback = RenderEffectDrawCallback() { + * val myOtherRenderEffect = createOtherRenderEffect() + * val chainEffect = RenderEffect.createChainEffect(renderEffect, myOtherRenderEffect) + * myView.setRenderEffect(chainEffect) + * } + * + * private val xEffect = XEffect(config, xEffectDrawingCallback) + * }</pre> + */ +interface RenderEffectDrawCallback { + fun onDraw(renderEffect: RenderEffect) +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt index 1c763e8c6108..211b84f25369 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt @@ -22,6 +22,8 @@ import android.animation.ValueAnimator import android.graphics.Paint import android.graphics.RenderEffect import android.view.View +import com.android.systemui.surfaceeffects.PaintDrawCallback +import com.android.systemui.surfaceeffects.RenderEffectDrawCallback import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader @@ -334,52 +336,31 @@ private constructor( ) } - companion object { + /** + * States of the loading effect animation. + * + * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN], + * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't necessarily + * mean the acceleration and deceleration in the animation curve. They simply mean each stage of + * the animation. (i.e. Intro, core, and rest) + */ + enum class AnimationState { + EASE_IN, + MAIN, + EASE_OUT, + NOT_PLAYING + } + + /** Optional callback that is triggered when the animation state changes. */ + interface AnimationStateChangedCallback { /** - * States of the loading effect animation. - * - * <p>The state is designed to be follow the order below: [AnimationState.EASE_IN], - * [AnimationState.MAIN], [AnimationState.EASE_OUT]. Note that ease in and out don't - * necessarily mean the acceleration and deceleration in the animation curve. They simply - * mean each stage of the animation. (i.e. Intro, core, and rest) + * A callback that's triggered when the [AnimationState] changes. Example usage is + * performing a cleanup when [AnimationState] becomes [NOT_PLAYING]. */ - enum class AnimationState { - EASE_IN, - MAIN, - EASE_OUT, - NOT_PLAYING - } - - /** Client must implement one of the draw callbacks. */ - interface PaintDrawCallback { - /** - * A callback with a [Paint] object that contains shader info, which is triggered every - * frame while animation is playing. Note that the [Paint] object here is always the - * same instance. - */ - fun onDraw(loadingPaint: Paint) - } - - interface RenderEffectDrawCallback { - /** - * A callback with a [RenderEffect] object that contains shader info, which is triggered - * every frame while animation is playing. Note that the [RenderEffect] instance is - * different each time to update shader uniforms. - */ - fun onDraw(loadingRenderEffect: RenderEffect) - } - - /** Optional callback that is triggered when the animation state changes. */ - interface AnimationStateChangedCallback { - /** - * A callback that's triggered when the [AnimationState] changes. Example usage is - * performing a cleanup when [AnimationState] becomes [NOT_PLAYING]. - */ - fun onStateChanged(oldState: AnimationState, newState: AnimationState) {} - } + fun onStateChanged(oldState: AnimationState, newState: AnimationState) {} + } + private companion object { private const val MS_TO_SEC = 0.001f - - private val TAG = LoadingEffect::class.java.simpleName } } 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 0f3d3dc2847f..d55d4e494980 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 @@ -160,7 +160,9 @@ private fun StandardLayout( FoldAware( modifier = modifier.padding( + start = 32.dp, top = 92.dp, + end = 32.dp, bottom = 48.dp, ), viewModel = viewModel, 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 a78c2c0d16c6..07c2d3c95e01 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 @@ -432,12 +432,12 @@ private fun offset( } } -private const val DOT_DIAMETER_DP = 16 -private const val SELECTED_DOT_DIAMETER_DP = 24 +private const val DOT_DIAMETER_DP = 14 +private const val SELECTED_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 1.5).toInt() private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83 private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750 -private const val LINE_STROKE_WIDTH_DP = 16 -private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = 13 +private const val LINE_STROKE_WIDTH_DP = DOT_DIAMETER_DP +private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 0.81f).toInt() private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50 private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33 private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617 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 bdd888f45182..4533f58c1c37 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 @@ -26,6 +26,7 @@ import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.res.R @@ -41,6 +42,11 @@ object Communal { } val sceneTransitions = transitions { + to(CommunalScenes.Communal, key = CommunalTransitionKeys.SimpleFade) { + spec = tween(durationMillis = 250) + fade(Communal.Elements.Scrim) + fade(Communal.Elements.Content) + } to(CommunalScenes.Communal) { spec = tween(durationMillis = 1000) translate(Communal.Elements.Content, Edge.Right) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt index 55f7f69a08d6..52cbffbc0177 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt @@ -19,8 +19,6 @@ package com.android.systemui.keyguard.ui.composable import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule -import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeWeatherClockBlueprintModule -import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockBlueprintModule import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule import dagger.Module @@ -31,8 +29,6 @@ import dagger.Module DefaultBlueprintModule::class, OptionalSectionModule::class, ShortcutsBesideUdfpsBlueprintModule::class, - SplitShadeWeatherClockBlueprintModule::class, - WeatherClockBlueprintModule::class, ], ) interface LockscreenSceneBlueprintModule diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt index acd9e3dc83cb..c6fe81af59b7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt @@ -25,6 +25,9 @@ import com.android.compose.animation.scene.transitions import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey +import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.transitioningToLargeClock +import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.transitioningToSmallClock +import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys.largeWeatherClockElementKeyList import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_MILLIS import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_START_DELAY_MILLIS import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceOutTransition.Companion.CLOCK_OUT_MILLIS @@ -34,30 +37,45 @@ import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSi object ClockTransition { val defaultClockTransitions = transitions { from(ClockScenes.smallClockScene, to = ClockScenes.largeClockScene) { - transitioningToLargeClock() + transitioningToLargeClock(largeClockElements = listOf(largeClockElementKey)) } from(ClockScenes.largeClockScene, to = ClockScenes.smallClockScene) { - transitioningToSmallClock() + transitioningToSmallClock(largeClockElements = listOf(largeClockElementKey)) } from(ClockScenes.splitShadeLargeClockScene, to = ClockScenes.largeClockScene) { - spec = tween(1000, easing = LinearEasing) + spec = tween(300, easing = LinearEasing) + } + + from(WeatherClockScenes.largeClockScene, to = ClockScenes.smallClockScene) { + transitioningToSmallClock(largeClockElements = largeWeatherClockElementKeyList) + } + + from(ClockScenes.smallClockScene, to = WeatherClockScenes.largeClockScene) { + transitioningToLargeClock(largeClockElements = largeWeatherClockElementKeyList) + } + + from( + WeatherClockScenes.largeClockScene, + to = WeatherClockScenes.splitShadeLargeClockScene + ) { + spec = tween(300, easing = LinearEasing) } } - private fun TransitionBuilder.transitioningToLargeClock() { + private fun TransitionBuilder.transitioningToLargeClock(largeClockElements: List<ElementKey>) { spec = tween(durationMillis = STATUS_AREA_MOVE_UP_MILLIS.toInt()) timestampRange( startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(), endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt() ) { - fade(largeClockElementKey) + largeClockElements.forEach { fade(it) } } timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(smallClockElementKey) } anchoredTranslate(smallClockElementKey, smartspaceElementKey) } - private fun TransitionBuilder.transitioningToSmallClock() { + private fun TransitionBuilder.transitioningToSmallClock(largeClockElements: List<ElementKey>) { spec = tween(durationMillis = STATUS_AREA_MOVE_DOWN_MILLIS.toInt()) timestampRange( startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(), @@ -66,7 +84,9 @@ object ClockTransition { fade(smallClockElementKey) } - timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(largeClockElementKey) } + timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { + largeClockElements.forEach { fade(it) } + } anchoredTranslate(smallClockElementKey, smartspaceElementKey) } } @@ -81,14 +101,26 @@ object ClockScenes { object ClockElementKeys { val largeClockElementKey = ElementKey("large-clock") val smallClockElementKey = ElementKey("small-clock") - val weatherSmallClockElementKey = ElementKey("weather-small-clock") val smartspaceElementKey = ElementKey("smart-space") } +object WeatherClockScenes { + val largeClockScene = SceneKey("large-weather-clock-scene") + val splitShadeLargeClockScene = SceneKey("split-shade-large-weather-clock-scene") +} + object WeatherClockElementKeys { val timeElementKey = ElementKey("weather-large-clock-time") val dateElementKey = ElementKey("weather-large-clock-date") val weatherIconElementKey = ElementKey("weather-large-clock-weather-icon") val temperatureElementKey = ElementKey("weather-large-clock-temperature") val dndAlarmElementKey = ElementKey("weather-large-clock-dnd-alarm") + val largeWeatherClockElementKeyList = + listOf( + timeElementKey, + dateElementKey, + weatherIconElementKey, + temperatureElementKey, + dndAlarmElementKey + ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt deleted file mode 100644 index cba54531713b..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt +++ /dev/null @@ -1,499 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.keyguard.ui.composable.blueprint - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -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.layout.Layout -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.IntRect -import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.SceneScope -import com.android.compose.modifiers.padding -import com.android.keyguard.KeyguardClockSwitch.LARGE -import com.android.systemui.Flags -import com.android.systemui.customization.R as customizationR -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.ui.composable.LockscreenLongPress -import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged -import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection -import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection -import com.android.systemui.keyguard.ui.composable.section.LockSection -import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection -import com.android.systemui.keyguard.ui.composable.section.NotificationSection -import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection -import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection -import com.android.systemui.keyguard.ui.composable.section.StatusBarSection -import com.android.systemui.keyguard.ui.composable.section.WeatherClockSection -import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel -import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.res.R -import com.android.systemui.shade.LargeScreenHeaderHelper -import dagger.Binds -import dagger.Module -import dagger.multibindings.IntoSet -import java.util.Optional -import javax.inject.Inject - -class WeatherClockBlueprint -@Inject -constructor( - private val viewModel: LockscreenContentViewModel, - private val statusBarSection: StatusBarSection, - private val weatherClockSection: WeatherClockSection, - private val smartSpaceSection: SmartSpaceSection, - private val notificationSection: NotificationSection, - private val lockSection: LockSection, - private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, - private val bottomAreaSection: BottomAreaSection, - private val settingsMenuSection: SettingsMenuSection, - private val clockInteractor: KeyguardClockInteractor, - private val mediaCarouselSection: MediaCarouselSection, - private val clockViewModel: KeyguardClockViewModel, -) : ComposableLockscreenSceneBlueprint { - - override val id: String = WEATHER_CLOCK_BLUEPRINT_ID - @Composable - override fun SceneScope.Content(modifier: Modifier) { - val isUdfpsVisible = viewModel.isUdfpsVisible - val burnIn = rememberBurnIn(clockInteractor) - val resources = LocalContext.current.resources - val currentClockState = clockViewModel.currentClock.collectAsState() - val areNotificationsVisible by viewModel.areNotificationsVisible.collectAsState() - LockscreenLongPress( - viewModel = viewModel.longPress, - modifier = modifier, - ) { onSettingsMenuPlaced -> - Layout( - content = { - // Constrained to above the lock icon. - Column( - modifier = Modifier.fillMaxWidth(), - ) { - with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - val currentClock = currentClockState.value - val clockSize by clockViewModel.clockSize.collectAsState() - with(weatherClockSection) { - if (currentClock == null) { - return@with - } - - if (clockSize == LARGE) { - Time( - clock = currentClock, - modifier = - Modifier.padding( - start = - dimensionResource( - customizationR.dimen.clock_padding_start - ) - ) - ) - } else { - SmallClock( - burnInParams = burnIn.parameters, - modifier = - Modifier.align(Alignment.Start) - .onTopPlacementChanged(burnIn.onSmallClockTopChanged), - clock = currentClock - ) - } - } - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - modifier = - Modifier.fillMaxWidth() - .padding( - top = { viewModel.getSmartSpacePaddingTop(resources) }, - ) - .padding( - bottom = - dimensionResource( - R.dimen.keyguard_status_view_bottom_margin - ), - ), - ) - } - - with(mediaCarouselSection) { MediaCarousel() } - - if (areNotificationsVisible) { - with(notificationSection) { - Notifications( - burnInParams = burnIn.parameters, - modifier = Modifier.fillMaxWidth().weight(weight = 1f) - ) - } - } - with(weatherClockSection) { - if (currentClock == null || clockSize != LARGE) { - return@with - } - LargeClockSectionBelowSmartspace(clock = currentClock) - } - - if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { - with(ambientIndicationSectionOptional.get()) { - AmbientIndication(modifier = Modifier.fillMaxWidth()) - } - } - } - - with(lockSection) { LockIcon() } - - // Aligned to bottom and constrained to below the lock icon. - Column(modifier = Modifier.fillMaxWidth()) { - if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { - with(ambientIndicationSectionOptional.get()) { - AmbientIndication(modifier = Modifier.fillMaxWidth()) - } - } - - with(bottomAreaSection) { - IndicationArea(modifier = Modifier.fillMaxWidth()) - } - } - - // Aligned to bottom and NOT constrained by the lock icon. - with(bottomAreaSection) { - Shortcut(isStart = true, applyPadding = true) - Shortcut(isStart = false, applyPadding = true) - } - with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) } - }, - modifier = Modifier.fillMaxSize(), - ) { measurables, constraints -> - check(measurables.size == 6) - val aboveLockIconMeasurable = measurables[0] - val lockIconMeasurable = measurables[1] - val belowLockIconMeasurable = measurables[2] - val startShortcutMeasurable = measurables[3] - val endShortcutMeasurable = measurables[4] - val settingsMenuMeasurable = measurables[5] - - val noMinConstraints = - constraints.copy( - minWidth = 0, - minHeight = 0, - ) - val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) - val lockIconBounds = - IntRect( - left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], - top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], - right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], - bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], - ) - - val aboveLockIconPlaceable = - aboveLockIconMeasurable.measure( - noMinConstraints.copy(maxHeight = lockIconBounds.top) - ) - val belowLockIconPlaceable = - belowLockIconMeasurable.measure( - noMinConstraints.copy( - maxHeight = - (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) - ) - ) - val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints) - val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints) - val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints) - - layout(constraints.maxWidth, constraints.maxHeight) { - aboveLockIconPlaceable.place( - x = 0, - y = 0, - ) - lockIconPlaceable.place( - x = lockIconBounds.left, - y = lockIconBounds.top, - ) - belowLockIconPlaceable.place( - x = 0, - y = constraints.maxHeight - belowLockIconPlaceable.height, - ) - startShortcutPleaceable.place( - x = 0, - y = constraints.maxHeight - startShortcutPleaceable.height, - ) - endShortcutPleaceable.place( - x = constraints.maxWidth - endShortcutPleaceable.width, - y = constraints.maxHeight - endShortcutPleaceable.height, - ) - settingsMenuPlaceable.place( - x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2, - y = constraints.maxHeight - settingsMenuPlaceable.height, - ) - } - } - } - } -} - -class SplitShadeWeatherClockBlueprint -@Inject -constructor( - private val viewModel: LockscreenContentViewModel, - private val statusBarSection: StatusBarSection, - private val smartSpaceSection: SmartSpaceSection, - private val notificationSection: NotificationSection, - private val lockSection: LockSection, - private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>, - private val bottomAreaSection: BottomAreaSection, - private val settingsMenuSection: SettingsMenuSection, - private val clockInteractor: KeyguardClockInteractor, - private val largeScreenHeaderHelper: LargeScreenHeaderHelper, - private val weatherClockSection: WeatherClockSection, - private val mediaCarouselSection: MediaCarouselSection, - private val clockViewModel: KeyguardClockViewModel, -) : ComposableLockscreenSceneBlueprint { - override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID - - @Composable - override fun SceneScope.Content(modifier: Modifier) { - val isUdfpsVisible = viewModel.isUdfpsVisible - val burnIn = rememberBurnIn(clockInteractor) - val resources = LocalContext.current.resources - val currentClockState = clockViewModel.currentClock.collectAsState() - LockscreenLongPress( - viewModel = viewModel.longPress, - modifier = modifier, - ) { onSettingsMenuPlaced -> - Layout( - content = { - // Constrained to above the lock icon. - Column( - modifier = Modifier.fillMaxSize(), - ) { - with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - Row( - modifier = Modifier.fillMaxSize(), - ) { - Column( - modifier = Modifier.fillMaxHeight().weight(weight = 1f), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - val currentClock = currentClockState.value - val clockSize by clockViewModel.clockSize.collectAsState() - with(weatherClockSection) { - if (currentClock == null) { - return@with - } - - if (clockSize == LARGE) { - Time( - clock = currentClock, - modifier = - Modifier.align(Alignment.Start) - .padding( - start = - dimensionResource( - customizationR.dimen - .clock_padding_start - ) - ) - ) - } else { - SmallClock( - burnInParams = burnIn.parameters, - modifier = - Modifier.align(Alignment.Start) - .onTopPlacementChanged( - burnIn.onSmallClockTopChanged - ), - clock = currentClock, - ) - } - } - with(smartSpaceSection) { - SmartSpace( - burnInParams = burnIn.parameters, - onTopChanged = burnIn.onSmartspaceTopChanged, - modifier = - Modifier.fillMaxWidth() - .padding( - top = { - viewModel.getSmartSpacePaddingTop(resources) - }, - ) - .padding( - bottom = - dimensionResource( - R.dimen - .keyguard_status_view_bottom_margin - ) - ), - ) - } - - with(mediaCarouselSection) { MediaCarousel() } - - with(weatherClockSection) { - if (currentClock == null || clockSize != LARGE) { - return@with - } - - LargeClockSectionBelowSmartspace(currentClock) - } - } - with(notificationSection) { - val splitShadeTopMargin: Dp = - if (Flags.centralizedStatusBarHeightFix()) { - largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp - } else { - dimensionResource( - id = R.dimen.large_screen_shade_header_height - ) - } - Notifications( - burnInParams = burnIn.parameters, - modifier = - Modifier.fillMaxHeight() - .weight(weight = 1f) - .padding(top = splitShadeTopMargin) - ) - } - } - - if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { - with(ambientIndicationSectionOptional.get()) { - AmbientIndication(modifier = Modifier.fillMaxWidth()) - } - } - } - - with(lockSection) { LockIcon() } - - // Aligned to bottom and constrained to below the lock icon. - Column(modifier = Modifier.fillMaxWidth()) { - if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { - with(ambientIndicationSectionOptional.get()) { - AmbientIndication(modifier = Modifier.fillMaxWidth()) - } - } - - with(bottomAreaSection) { - IndicationArea(modifier = Modifier.fillMaxWidth()) - } - } - - // Aligned to bottom and NOT constrained by the lock icon. - with(bottomAreaSection) { - Shortcut(isStart = true, applyPadding = true) - Shortcut(isStart = false, applyPadding = true) - } - with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) } - }, - modifier = Modifier.fillMaxSize(), - ) { measurables, constraints -> - check(measurables.size == 6) - val aboveLockIconMeasurable = measurables[0] - val lockIconMeasurable = measurables[1] - val belowLockIconMeasurable = measurables[2] - val startShortcutMeasurable = measurables[3] - val endShortcutMeasurable = measurables[4] - val settingsMenuMeasurable = measurables[5] - - val noMinConstraints = - constraints.copy( - minWidth = 0, - minHeight = 0, - ) - val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) - val lockIconBounds = - IntRect( - left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], - top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], - right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], - bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], - ) - - val aboveLockIconPlaceable = - aboveLockIconMeasurable.measure( - noMinConstraints.copy(maxHeight = lockIconBounds.top) - ) - val belowLockIconPlaceable = - belowLockIconMeasurable.measure( - noMinConstraints.copy( - maxHeight = - (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) - ) - ) - val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints) - val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints) - val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints) - - layout(constraints.maxWidth, constraints.maxHeight) { - aboveLockIconPlaceable.place( - x = 0, - y = 0, - ) - lockIconPlaceable.place( - x = lockIconBounds.left, - y = lockIconBounds.top, - ) - belowLockIconPlaceable.place( - x = 0, - y = constraints.maxHeight - belowLockIconPlaceable.height, - ) - startShortcutPleaceable.place( - x = 0, - y = constraints.maxHeight - startShortcutPleaceable.height, - ) - endShortcutPleaceable.place( - x = constraints.maxWidth - endShortcutPleaceable.width, - y = constraints.maxHeight - endShortcutPleaceable.height, - ) - settingsMenuPlaceable.place( - x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2, - y = constraints.maxHeight - settingsMenuPlaceable.height, - ) - } - } - } - } -} - -@Module -interface WeatherClockBlueprintModule { - @Binds - @IntoSet - fun blueprint(blueprint: WeatherClockBlueprint): ComposableLockscreenSceneBlueprint -} - -@Module -interface SplitShadeWeatherClockBlueprintModule { - @Binds - @IntoSet - fun blueprint(blueprint: SplitShadeWeatherClockBlueprint): ComposableLockscreenSceneBlueprint -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt index 467dbca759c8..97d5b41000de 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt @@ -32,7 +32,6 @@ import androidx.core.content.res.ResourcesCompat import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.systemui.animation.view.LaunchableImageView -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea @@ -44,7 +43,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.Flow @@ -56,7 +54,6 @@ constructor( private val vibratorHelper: VibratorHelper, private val indicationController: KeyguardIndicationController, private val indicationAreaViewModel: KeyguardIndicationAreaViewModel, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, ) { /** * Renders a single lockscreen shortcut. @@ -164,7 +161,6 @@ constructor( transitionAlpha, falsingManager, vibratorHelper, - mainImmediateDispatcher, ) { indicationController.showTransientIndication(it) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 48684a02bd19..9f02201f1d81 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -34,7 +34,6 @@ import com.android.keyguard.LockIconView import com.android.keyguard.LockIconViewController import com.android.systemui.biometrics.AuthController import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags @@ -51,14 +50,12 @@ import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.VibratorHelper import dagger.Lazy import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope class LockSection @Inject constructor( @Application private val applicationScope: CoroutineScope, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, private val windowManager: WindowManager, private val authController: AuthController, private val featureFlags: FeatureFlagsClassic, @@ -96,7 +93,6 @@ constructor( deviceEntryBackgroundViewModel.get(), falsingManager.get(), vibratorHelper.get(), - mainImmediateDispatcher, ) } } else { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index f8e63411ed52..0934b20562b4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -16,9 +16,11 @@ package com.android.systemui.keyguard.ui.composable.section +import android.content.Context import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable @@ -26,6 +28,10 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout @@ -36,6 +42,7 @@ import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallCl import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeSmallClockScene import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition +import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockScenes import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import javax.inject.Inject @@ -47,6 +54,7 @@ constructor( private val smartSpaceSection: SmartSpaceSection, private val mediaCarouselSection: MediaCarouselSection, private val clockSection: DefaultClockSection, + private val weatherClockSection: WeatherClockSection, private val clockInteractor: KeyguardClockInteractor, ) { @Composable @@ -64,6 +72,10 @@ constructor( splitShadeSmallClockScene KeyguardClockViewModel.ClockLayout.LARGE_CLOCK -> largeClockScene KeyguardClockViewModel.ClockLayout.SMALL_CLOCK -> smallClockScene + KeyguardClockViewModel.ClockLayout.WEATHER_LARGE_CLOCK -> + WeatherClockScenes.largeClockScene + KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK -> + WeatherClockScenes.splitShadeLargeClockScene } SceneTransitionLayout( @@ -86,6 +98,12 @@ constructor( scene(smallClockScene) { SmallClockWithSmartSpace() } scene(largeClockScene) { LargeClockWithSmartSpace() } + + scene(WeatherClockScenes.largeClockScene) { WeatherLargeClockWithSmartSpace() } + + scene(WeatherClockScenes.splitShadeLargeClockScene) { + WeatherLargeClockWithSmartSpace(modifier = Modifier.fillMaxWidth(0.5f)) + } } } @@ -146,4 +164,50 @@ constructor( } } } + + @Composable + private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) { + val burnIn = rememberBurnIn(clockInteractor) + val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState() + val currentClockState = clockViewModel.currentClock.collectAsState() + + LaunchedEffect(isLargeClockVisible) { + if (isLargeClockVisible) { + burnIn.onSmallClockTopChanged(null) + } + } + + Column(modifier = modifier) { + val currentClock = currentClockState.value ?: return@Column + with(weatherClockSection) { Time(clock = currentClock, modifier = Modifier) } + val density = LocalDensity.current + val context = LocalContext.current + + with(smartSpaceSection) { + SmartSpace( + burnInParams = burnIn.parameters, + onTopChanged = burnIn.onSmartspaceTopChanged, + modifier = + Modifier.heightIn( + min = getDimen(context, "enhanced_smartspace_height", density) + ) + ) + } + with(weatherClockSection) { LargeClockSectionBelowSmartspace(clock = currentClock) } + } + } + + /* + * Use this function to access dimen which cannot be access by R.dimen directly + * Currently use to access dimen from BcSmartspace + * @param name Name of resources + * @param density Density required to convert dimen from Int To Dp + */ + private fun getDimen(context: Context, name: String, density: Density): Dp { + val res = context.packageManager.getResourcesForApplication(context.packageName) + val id = res.getIdentifier(name, "dimen", context.packageName) + var dimen: Dp + with(density) { dimen = (if (id == 0) 0 else res.getDimensionPixelSize(id)).toDp() } + return dimen + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt index d3584539b3fa..a7bb308ada5c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.composable.section +import android.view.View import android.view.ViewGroup import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.IntrinsicSize @@ -27,18 +28,14 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding -import com.android.systemui.customization.R -import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.weatherSmallClockElementKey +import com.android.systemui.customization.R as customizationR import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys -import com.android.systemui.keyguard.ui.composable.modifier.burnInAware import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel -import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.plugins.clocks.ClockController import javax.inject.Inject @@ -55,12 +52,19 @@ constructor( clock: ClockController, modifier: Modifier = Modifier, ) { - WeatherElement( - weatherClockElementViewId = R.id.weather_clock_time, - clock = clock, - elementKey = WeatherClockElementKeys.timeElementKey, - modifier = modifier.wrapContentSize(), - ) + Row( + modifier = + Modifier.padding( + horizontal = dimensionResource(customizationR.dimen.clock_padding_start) + ) + ) { + WeatherElement( + weatherClockElementViewId = customizationR.id.weather_clock_time, + clock = clock, + elementKey = WeatherClockElementKeys.timeElementKey, + modifier = modifier, + ) + } } @Composable @@ -69,7 +73,7 @@ constructor( modifier: Modifier = Modifier, ) { WeatherElement( - weatherClockElementViewId = R.id.weather_clock_date, + weatherClockElementViewId = customizationR.id.weather_clock_date, clock = clock, elementKey = WeatherClockElementKeys.dateElementKey, modifier = modifier, @@ -82,7 +86,7 @@ constructor( modifier: Modifier = Modifier, ) { WeatherElement( - weatherClockElementViewId = R.id.weather_clock_weather_icon, + weatherClockElementViewId = customizationR.id.weather_clock_weather_icon, clock = clock, elementKey = WeatherClockElementKeys.weatherIconElementKey, modifier = modifier.wrapContentSize(), @@ -95,7 +99,7 @@ constructor( modifier: Modifier = Modifier, ) { WeatherElement( - weatherClockElementViewId = R.id.weather_clock_alarm_dnd, + weatherClockElementViewId = customizationR.id.weather_clock_alarm_dnd, clock = clock, elementKey = WeatherClockElementKeys.dndAlarmElementKey, modifier = modifier.wrapContentSize(), @@ -108,7 +112,7 @@ constructor( modifier: Modifier = Modifier, ) { WeatherElement( - weatherClockElementViewId = R.id.weather_clock_temperature, + weatherClockElementViewId = customizationR.id.weather_clock_temperature, clock = clock, elementKey = WeatherClockElementKeys.temperatureElementKey, modifier = modifier.wrapContentSize(), @@ -126,12 +130,16 @@ constructor( content { AndroidView( factory = { - val view = - clock.largeClock.layout.views.first { - it.id == weatherClockElementViewId - } - (view.parent as? ViewGroup)?.removeView(view) - view + try { + val view = + clock.largeClock.layout.views.first { + it.id == weatherClockElementViewId + } + (view.parent as? ViewGroup)?.removeView(view) + view + } catch (e: NoSuchElementException) { + View(it) + } }, update = {}, modifier = modifier @@ -147,46 +155,22 @@ constructor( Row( modifier = Modifier.height(IntrinsicSize.Max) - .padding(horizontal = dimensionResource(R.dimen.clock_padding_start)) + .padding( + horizontal = dimensionResource(customizationR.dimen.clock_padding_start) + ) ) { Date(clock = clock, modifier = Modifier.wrapContentSize()) - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = + Modifier.fillMaxSize() + .padding( + start = dimensionResource(customizationR.dimen.clock_padding_start) + ) + ) { Weather(clock = clock, modifier = Modifier.align(Alignment.TopStart)) Temperature(clock = clock, modifier = Modifier.align(Alignment.BottomEnd)) DndAlarmStatus(clock = clock, modifier = Modifier.align(Alignment.TopEnd)) } } } - - @Composable - fun SceneScope.SmallClock( - burnInParams: BurnInParameters, - modifier: Modifier = Modifier, - clock: ClockController, - ) { - val localContext = LocalContext.current - MovableElement(key = weatherSmallClockElementKey, modifier) { - content { - AndroidView( - factory = { - val view = clock.smallClock.view - if (view.parent != null) { - (view.parent as? ViewGroup)?.removeView(view) - } - view - }, - modifier = - modifier - .height(dimensionResource(R.dimen.small_clock_height)) - .padding(start = dimensionResource(R.dimen.clock_padding_start)) - .padding(top = { viewModel.getSmallClockTopMargin(localContext) }) - .burnInAware( - viewModel = aodBurnInViewModel, - params = burnInParams, - ), - update = {}, - ) - } - } - } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt index ccb5d367c357..fa052e8e3035 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt @@ -17,15 +17,12 @@ package com.android.systemui.volume.panel.component.anc import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria -import com.android.systemui.volume.panel.component.anc.ui.composable.AncPopup -import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel -import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent +import com.android.systemui.volume.panel.component.anc.ui.composable.AncButtonComponent import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent import dagger.Binds import dagger.Module -import dagger.Provides import dagger.multibindings.IntoMap import dagger.multibindings.StringKey @@ -40,14 +37,8 @@ interface AncModule { criteria: AncAvailabilityCriteria ): ComponentAvailabilityCriteria - companion object { - - @Provides - @IntoMap - @StringKey(VolumePanelComponents.ANC) - fun provideVolumePanelUiComponent( - viewModel: AncViewModel, - popup: AncPopup, - ): VolumePanelUiComponent = ButtonComponent(viewModel.button, popup::show) - } + @Binds + @IntoMap + @StringKey(VolumePanelComponents.ANC) + fun bindVolumePanelUiComponent(component: AncButtonComponent): VolumePanelUiComponent } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt new file mode 100644 index 000000000000..00225fc3577a --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.anc.ui.composable + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +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.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.android.systemui.res.R +import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel +import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent +import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope +import javax.inject.Inject + +class AncButtonComponent +@Inject +constructor( + private val viewModel: AncViewModel, + private val ancPopup: AncPopup, +) : ComposeVolumePanelUiComponent { + + @Composable + override fun VolumePanelComposeScope.Content(modifier: Modifier) { + val slice by viewModel.buttonSlice.collectAsState() + val label = stringResource(R.string.volume_panel_noise_control_title) + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + SliceAndroidView( + modifier = + Modifier.height(64.dp) + .fillMaxWidth() + .semantics { + role = Role.Button + contentDescription = label + } + .clip(RoundedCornerShape(28.dp)), + slice = slice, + onWidthChanged = viewModel::onButtonSliceWidthChanged, + onClick = { ancPopup.show(null) } + ) + Text( + modifier = Modifier.clearAndSetSemantics {}, + text = label, + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt index 9f0da004730d..2af042aac5b4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt @@ -16,9 +16,6 @@ package com.android.systemui.volume.panel.component.anc.ui.composable -import android.content.Context -import android.view.ContextThemeWrapper -import android.view.View import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme @@ -30,9 +27,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.viewinterop.AndroidView import androidx.slice.Slice -import androidx.slice.widget.SliceView import com.android.systemui.animation.Expandable import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog @@ -49,7 +44,7 @@ constructor( ) { /** Shows a popup with the [expandable] animation. */ - fun show(expandable: Expandable) { + fun show(expandable: Expandable?) { volumePanelPopup.show(expandable, { Title() }, { Content(it) }) } @@ -66,49 +61,18 @@ constructor( @Composable private fun Content(dialog: SystemUIDialog) { - val slice: Slice? by viewModel.slice.collectAsState() + val isAvailable by viewModel.isAvailable.collectAsState(true) - if (slice == null) { + if (!isAvailable) { SideEffect { dialog.dismiss() } return } - AndroidView<SliceView>( + val slice by viewModel.popupSlice.collectAsState() + SliceAndroidView( modifier = Modifier.fillMaxWidth(), - factory = { context: Context -> - SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel)) - .apply { - mode = SliceView.MODE_LARGE - isScrollable = false - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - setShowTitleItems(true) - addOnLayoutChangeListener( - OnWidthChangedLayoutListener(viewModel::changeSliceWidth) - ) - } - }, - update = { sliceView: SliceView -> sliceView.slice = slice } + slice = slice, + onWidthChanged = viewModel::onPopupSliceWidthChanged ) } - - private class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) : - View.OnLayoutChangeListener { - override fun onLayoutChange( - v: View?, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - val newWidth = right - left - val oldWidth = oldRight - oldLeft - if (oldWidth != newWidth) { - widthChanged(newWidth) - } - } - } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt new file mode 100644 index 000000000000..f354b80692f5 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.anc.ui.composable + +import android.annotation.SuppressLint +import android.content.Context +import android.view.ContextThemeWrapper +import android.view.MotionEvent +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import androidx.slice.Slice +import androidx.slice.widget.SliceView +import com.android.systemui.res.R + +@Composable +fun SliceAndroidView( + slice: Slice?, + modifier: Modifier = Modifier, + onWidthChanged: ((Int) -> Unit)? = null, + onClick: (() -> Unit)? = null, +) { + AndroidView( + modifier = modifier, + factory = { context: Context -> + ClickableSliceView( + ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel), + onClick, + ) + .apply { + mode = SliceView.MODE_LARGE + isScrollable = false + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + setShowTitleItems(true) + if (onWidthChanged != null) { + addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged)) + } + if (onClick != null) { + setOnClickListener { onClick() } + } + } + }, + update = { sliceView: SliceView -> sliceView.slice = slice } + ) +} + +class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) : + View.OnLayoutChangeListener { + + override fun onLayoutChange( + v: View?, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + val newWidth = right - left + val oldWidth = oldRight - oldLeft + if (oldWidth != newWidth) { + widthChanged(newWidth) + } + } +} + +/** + * [SliceView] that prioritises [onClick] when its clicked instead of passing the event to the slice + * first. + */ +@SuppressLint("ViewConstructor") // only used in this class +private class ClickableSliceView( + context: Context, + private val onClick: (() -> Unit)?, +) : SliceView(context) { + + init { + if (onClick != null) { + setOnClickListener {} + } + } + + override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { + return onClick != null || super.onInterceptTouchEvent(ev) + } + + override fun onClick(v: View?) { + onClick?.let { it() } ?: super.onClick(v) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt index 9f9bc623a6b3..b489dfc2e39b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt @@ -59,7 +59,7 @@ constructor( * @param content is the popup body */ fun show( - expandable: Expandable, + expandable: Expandable?, title: @Composable (SystemUIDialog) -> Unit, content: @Composable (SystemUIDialog) -> Unit, ) { @@ -70,7 +70,7 @@ constructor( ) { PopupComposable(it, title, content) } - val controller = expandable.dialogTransitionController() + val controller = expandable?.dialogTransitionController() if (controller == null) { dialog.show() } else { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 19d3f599ef31..28cd37ea9327 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -16,13 +16,15 @@ package com.android.systemui.volume.panel.component.volume.ui.composable +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue @@ -84,27 +86,29 @@ fun VolumeSlider( valueRange = state.valueRange, onValueChange = onValueChange, enabled = state.isEnabled, - icon = { isDragging -> - if (isDragging) { - Text(text = state.valueText, color = LocalContentColor.current) - } else { - state.icon?.let { - SliderIcon( - icon = it, - onIconTapped = onIconTapped, - isTappable = state.isMutable, - ) - } + icon = { + state.icon?.let { + SliderIcon( + icon = it, + onIconTapped = onIconTapped, + isTappable = state.isMutable, + ) } }, colors = sliderColors, - label = { - VolumeSliderContent( - modifier = Modifier, - label = state.label, - isEnabled = state.isEnabled, - disabledMessage = state.disabledMessage, - ) + label = { isDragging -> + AnimatedVisibility( + visible = !isDragging, + enter = fadeIn(tween(150)), + exit = fadeOut(tween(150)), + ) { + VolumeSliderContent( + modifier = Modifier, + label = state.label, + isEnabled = state.isEnabled, + disabledMessage = state.disabledMessage, + ) + } } ) } 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 b1cfdcf07977..dbec059715b4 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 @@ -204,15 +204,16 @@ internal class SceneTransitionLayoutImpl( } // Handle back events. - // TODO(b/290184746): Make sure that this works with SystemUI once we use - // SceneTransitionLayout in Flexiglass. - scene(state.transitionState.currentScene).userActions[Back]?.let { result -> - // TODO(b/290184746): Handle predictive back and use result.distance if - // specified. - BackHandler { - val targetScene = result.toScene - if (state.canChangeScene(targetScene)) { - with(state) { coroutineScope.onChangeScene(targetScene) } + val targetSceneForBackOrNull = + scene(state.transitionState.currentScene).userActions[Back]?.toScene + BackHandler( + enabled = targetSceneForBackOrNull != null, + ) { + targetSceneForBackOrNull?.let { targetSceneForBack -> + // TODO(b/290184746): Handle predictive back and use result.distance if + // specified. + if (state.canChangeScene(targetSceneForBack)) { + with(state) { coroutineScope.onChangeScene(targetSceneForBack) } } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 1120914fec7c..2d4b63ef2c27 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -66,6 +66,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor 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.model.FakeSceneDataSource import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource @@ -209,7 +210,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository()) featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) mSetFlagsRule.enableFlags( @@ -217,7 +217,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { ) mSetFlagsRule.disableFlags( FLAG_SIDEFPS_CONTROLLER_REFACTOR, - AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR + AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT, ) keyguardPasswordViewController = @@ -267,7 +268,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { falsingManager, userSwitcherController, featureFlags, - kosmos.fakeSceneContainerFlags, + kosmos.sceneContainerFlags, globalSettings, sessionTracker, Optional.of(sideFpsController), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 4950b96b077f..85774c67bccb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -537,4 +537,65 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height()) } } + + @Test + fun addViewPending_layoutIsNotUpdated() = + testScope.runTest { + withReasonSuspend(REASON_AUTH_KEYGUARD) { + mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) + mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + + // GIVEN going to sleep + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.OFF, + to = KeyguardState.GONE, + testScope = this, + ) + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_SLEEP, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + + // WHEN a request comes to show the view + controllerOverlay.show(udfpsController, overlayParams) + runCurrent() + + // THEN the view does not get added immediately + verify(windowManager, never()).addView(any(), any()) + + // WHEN updateOverlayParams gets called when the view is pending to be added + controllerOverlay.updateOverlayParams(overlayParams) + + // THEN the view layout is never updated + verify(windowManager, never()).updateViewLayout(any(), any()) + + // CLEANUPL we hide to end the job that listens for the finishedGoingToSleep signal + controllerOverlay.hide() + } + } + + @Test + fun updateOverlayParams_viewLayoutUpdated() = + testScope.runTest { + withReasonSuspend(REASON_AUTH_KEYGUARD) { + mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.POWER_BUTTON, + lastSleepReason = WakeSleepReason.OTHER, + ) + runCurrent() + controllerOverlay.show(udfpsController, overlayParams) + runCurrent() + verify(windowManager).addView(any(), any()) + + // WHEN updateOverlayParams gets called + controllerOverlay.updateOverlayParams(overlayParams) + + // THEN the view layout is updated + verify(windowManager, never()).updateViewLayout(any(), any()) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index a944afb70f38..76f15d2ed257 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -120,19 +120,20 @@ class CommunalSceneStartableTest : SysuiTestCase() { } @Test - fun exitingDream_forceCommunalScene() = + fun occluded_forceBlankScene() = with(kosmos) { testScope.runTest { val scene by collectLastValue(communalInteractor.desiredScene) - assertThat(scene).isEqualTo(CommunalScenes.Blank) + communalInteractor.changeScene(CommunalScenes.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) updateDocked(true) fakeKeyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.DREAMING, - to = KeyguardState.LOCKSCREEN, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, testScope = this ) - assertThat(scene).isEqualTo(CommunalScenes.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Blank) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index f71121c43ff8..ce7b60e86f8f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -23,6 +23,7 @@ import android.app.admin.devicePolicyManager import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.content.pm.UserInfo +import android.os.UserManager.USER_TYPE_PROFILE_MANAGED import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.provider.Settings @@ -59,6 +60,7 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE) setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE) + setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE) underTest = kosmos.communalSettingsRepository } @@ -133,6 +135,30 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { @EnableFlags(FLAG_COMMUNAL_HUB) @Test + fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() = + testScope.runTest { + val widgetsAllowedForWorkProfile by + collectLastValue(underTest.getAllowedByDevicePolicy(WORK_PROFILE)) + assertThat(widgetsAllowedForWorkProfile).isTrue() + + setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL) + assertThat(widgetsAllowedForWorkProfile).isFalse() + } + + @EnableFlags(FLAG_COMMUNAL_HUB) + @Test + fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() = + testScope.runTest { + val enabledStateForPrimaryUser by + collectLastValue(underTest.getEnabledState(PRIMARY_USER)) + assertThat(enabledStateForPrimaryUser?.enabled).isTrue() + + setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL) + assertThat(enabledStateForPrimaryUser?.enabled).isTrue() + } + + @EnableFlags(FLAG_COMMUNAL_HUB) + @Test fun hubIsDisabledByUserAndDevicePolicy() = testScope.runTest { val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) @@ -189,5 +215,13 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { val PRIMARY_USER = UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN) val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0) + val WORK_PROFILE = + UserInfo( + 10, + "work", + /* iconPath= */ "", + /* flags= */ 0, + USER_TYPE_PROFILE_MANAGED, + ) } } 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 e7ccde26e161..f21e9697c91f 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 @@ -17,6 +17,8 @@ package com.android.systemui.communal.domain.interactor +import android.app.admin.DevicePolicyManager +import android.app.admin.devicePolicyManager import android.app.smartspace.SmartspaceTarget import android.appwidget.AppWidgetProviderInfo import android.content.Intent @@ -32,6 +34,7 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository @@ -71,6 +74,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat @@ -929,7 +933,6 @@ class CommunalInteractorTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(true) keyguardRepository.setKeyguardOccluded(false) tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - userRepository.setSelectedUserInfo(mainUser) val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) userRepository.setUserInfos(userInfos) @@ -937,6 +940,7 @@ class CommunalInteractorTest : SysuiTestCase() { userInfos = userInfos, selectedUserIndex = 0, ) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) runCurrent() // Widgets available. @@ -955,7 +959,6 @@ class CommunalInteractorTest : SysuiTestCase() { AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, mainUser.id ) - runCurrent() // Only the keyguard widget is enabled. assertThat(widgetContent).hasSize(3) @@ -974,7 +977,6 @@ class CommunalInteractorTest : SysuiTestCase() { keyguardRepository.setKeyguardShowing(true) keyguardRepository.setKeyguardOccluded(false) tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - userRepository.setSelectedUserInfo(mainUser) val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) userRepository.setUserInfos(userInfos) @@ -982,6 +984,7 @@ class CommunalInteractorTest : SysuiTestCase() { userInfos = userInfos, selectedUserIndex = 0, ) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) runCurrent() // Widgets available. @@ -1001,7 +1004,6 @@ class CommunalInteractorTest : SysuiTestCase() { AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, mainUser.id ) - runCurrent() // All widgets are enabled. assertThat(widgetContent).hasSize(3) @@ -1011,6 +1013,79 @@ class CommunalInteractorTest : SysuiTestCase() { } } + @Test + fun filterWidgets_whenDisallowedByDevicePolicyForWorkProfile() = + testScope.runTest { + // Keyguard showing, and tutorial completed. + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + + val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) + userRepository.setUserInfos(userInfos) + userTracker.set( + userInfos = userInfos, + selectedUserIndex = 0, + ) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) + runCurrent() + + val widgetContent by collectLastValue(underTest.widgetContent) + // Given three widgets, and one of them is associated with work profile. + val widget1 = createWidgetForUser(1, USER_INFO_WORK.id) + val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id) + val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id) + val widgets = listOf(widget1, widget2, widget3) + widgetRepository.setCommunalWidgets(widgets) + + setKeyguardFeaturesDisabled( + USER_INFO_WORK, + DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL + ) + + // Widget under work profile is filtered out and the remaining two link to main user id. + assertThat(widgetContent).hasSize(2) + widgetContent!!.forEach { model -> + assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id) + } + } + + @Test + fun filterWidgets_whenAllowedByDevicePolicyForWorkProfile() = + testScope.runTest { + // Keyguard showing, and tutorial completed. + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + + val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) + userRepository.setUserInfos(userInfos) + userTracker.set( + userInfos = userInfos, + selectedUserIndex = 0, + ) + userRepository.setSelectedUserInfo(MAIN_USER_INFO) + runCurrent() + + val widgetContent by collectLastValue(underTest.widgetContent) + // Given three widgets, and one of them is associated with work profile. + val widget1 = createWidgetForUser(1, USER_INFO_WORK.id) + val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id) + val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id) + val widgets = listOf(widget1, widget2, widget3) + widgetRepository.setCommunalWidgets(widgets) + + setKeyguardFeaturesDisabled( + USER_INFO_WORK, + DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE + ) + + // Widget under work profile is available. + assertThat(widgetContent).hasSize(3) + assertThat(widgetContent!![0].providerInfo.profile?.identifier) + .isEqualTo(USER_INFO_WORK.id) + } + private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget { val timer = mock(SmartspaceTarget::class.java) whenever(timer.smartspaceTargetId).thenReturn(id) @@ -1020,6 +1095,15 @@ class CommunalInteractorTest : SysuiTestCase() { return timer } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { + whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) + .thenReturn(disabledFlags) + kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + ) + } + private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel = mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(appWidgetId) @@ -1044,6 +1128,13 @@ class CommunalInteractorTest : SysuiTestCase() { private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) - val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE) + val USER_INFO_WORK = + UserInfo( + 10, + "work", + /* iconPath= */ "", + /* flags= */ 0, + UserManager.USER_TYPE_PROFILE_MANAGED, + ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index ff7293d09946..8bc6a00a3426 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -820,7 +820,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun isAuthenticatedIsResetToFalseWhenTransitioningToGone() = + fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() = testScope.runTest { initCollectors() allPreconditionsToRunFaceAuthAreTrue() @@ -833,13 +833,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authenticated()).isTrue() - keyguardTransitionRepository.sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - ) - ) + keyguardRepository.keyguardDoneAnimationsFinished() assertThat(authenticated()).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt new file mode 100644 index 000000000000..33a17e8c56dd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.platform.test.annotations.DisableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardClockSwitch.LARGE +import com.android.keyguard.KeyguardClockSwitch.SMALL +import com.android.systemui.Flags as AConfigFlags +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardClockRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.data.repository.mediaFilterRepository +import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyguardClockInteractorTest : SysuiTestCase() { + private lateinit var kosmos: Kosmos + private lateinit var underTest: KeyguardClockInteractor + private lateinit var testScope: TestScope + + @Before + fun setup() { + kosmos = testKosmos() + testScope = kosmos.testScope + underTest = kosmos.keyguardClockInteractor + } + + @Test + @DisableFlags(AConfigFlags.FLAG_SCENE_CONTAINER) + fun clockSize_sceneContainerFlagOff_basedOnRepository() = + testScope.runTest { + val value by collectLastValue(underTest.clockSize) + kosmos.keyguardClockRepository.setClockSize(LARGE) + assertThat(value).isEqualTo(LARGE) + + kosmos.keyguardClockRepository.setClockSize(SMALL) + assertThat(value).isEqualTo(SMALL) + } + + @Test + @DisableFlags(AConfigFlags.FLAG_SCENE_CONTAINER) + fun clockShouldBeCentered_sceneContainerFlagOff_basedOnRepository() = + testScope.runTest { + val value by collectLastValue(underTest.clockShouldBeCentered) + kosmos.keyguardInteractor.setClockShouldBeCentered(true) + assertThat(value).isEqualTo(true) + + kosmos.keyguardInteractor.setClockShouldBeCentered(false) + assertThat(value).isEqualTo(false) + } + + @Test + @EnableSceneContainer + fun clockSize_forceSmallClock_SMALL() = + testScope.runTest { + val value by collectLastValue(underTest.clockSize) + kosmos.fakeKeyguardClockRepository.setShouldForceSmallClock(true) + kosmos.fakeFeatureFlagsClassic.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true) + transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN) + assertThat(value).isEqualTo(SMALL) + } + + @Test + @EnableSceneContainer + fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasNotifs_SMALL() = + testScope.runTest { + val value by collectLastValue(underTest.clockSize) + kosmos.shadeRepository.setShadeMode(ShadeMode.Single) + kosmos.activeNotificationListRepository.setActiveNotifs(1) + assertThat(value).isEqualTo(SMALL) + } + + @Test + @EnableSceneContainer + fun clockSize_SceneContainerFlagOn_shadeModeSingle_hasMedia_SMALL() = + testScope.runTest { + val value by collectLastValue(underTest.clockSize) + kosmos.shadeRepository.setShadeMode(ShadeMode.Single) + val userMedia = MediaData().copy(active = true) + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + assertThat(value).isEqualTo(SMALL) + } + + @Test + @EnableSceneContainer + fun clockSize_SceneContainerFlagOn_shadeModeSplit_isMediaVisible_SMALL() = + testScope.runTest { + val value by collectLastValue(underTest.clockSize) + val userMedia = MediaData().copy(active = true) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + kosmos.keyguardRepository.setIsDozing(false) + assertThat(value).isEqualTo(SMALL) + } + + @Test + @EnableSceneContainer + fun clockSize_SceneContainerFlagOn_shadeModeSplit_noMedia_LARGE() = + testScope.runTest { + val value by collectLastValue(underTest.clockSize) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + kosmos.keyguardRepository.setIsDozing(false) + assertThat(value).isEqualTo(LARGE) + } + + @Test + @EnableSceneContainer + fun clockSize_SceneContainerFlagOn_shadeModeSplit_isDozing_LARGE() = + testScope.runTest { + val value by collectLastValue(underTest.clockSize) + val userMedia = MediaData().copy(active = true) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + kosmos.keyguardRepository.setIsDozing(true) + assertThat(value).isEqualTo(LARGE) + } + + @Test + @EnableSceneContainer + fun clockShouldBeCentered_sceneContainerFlagOn_notSplitMode_true() = + testScope.runTest { + val value by collectLastValue(underTest.clockShouldBeCentered) + kosmos.shadeRepository.setShadeMode(ShadeMode.Single) + assertThat(value).isEqualTo(true) + } + + @Test + @EnableSceneContainer + fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_noActiveNotifications_true() = + testScope.runTest { + val value by collectLastValue(underTest.clockShouldBeCentered) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + kosmos.activeNotificationListRepository.setActiveNotifs(0) + assertThat(value).isEqualTo(true) + } + + @Test + @EnableSceneContainer + fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_isActiveDreamLockscreenHosted_true() = + testScope.runTest { + val value by collectLastValue(underTest.clockShouldBeCentered) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + kosmos.activeNotificationListRepository.setActiveNotifs(1) + kosmos.keyguardRepository.setIsActiveDreamLockscreenHosted(true) + assertThat(value).isEqualTo(true) + } + + @Test + @EnableSceneContainer + fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_hasPulsingNotifications_false() = + testScope.runTest { + val value by collectLastValue(underTest.clockShouldBeCentered) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + kosmos.activeNotificationListRepository.setActiveNotifs(1) + kosmos.headsUpNotificationRepository.headsUpAnimatingAway.value = true + kosmos.keyguardRepository.setIsDozing(true) + assertThat(value).isEqualTo(false) + } + + @Test + @EnableSceneContainer + fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_onAod_true() = + testScope.runTest { + val value by collectLastValue(underTest.clockShouldBeCentered) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + kosmos.activeNotificationListRepository.setActiveNotifs(1) + transitionTo(KeyguardState.LOCKSCREEN, KeyguardState.AOD) + assertThat(value).isEqualTo(true) + } + + @Test + @EnableSceneContainer + fun clockShouldBeCentered_sceneContainerFlagOn_splitMode_offAod_false() = + testScope.runTest { + val value by collectLastValue(underTest.clockShouldBeCentered) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + kosmos.activeNotificationListRepository.setActiveNotifs(1) + transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN) + assertThat(value).isEqualTo(false) + } + + private suspend fun transitionTo(from: KeyguardState, to: KeyguardState) { + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(from, to, 0f, TransitionState.STARTED) + ) + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(from, to, 0.5f, TransitionState.RUNNING) + ) + kosmos.fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep(from, to, 1f, TransitionState.FINISHED) + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 6d7a0a96a71b..1dd5d073bef3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory 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.model.Scenes import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor @@ -75,7 +76,7 @@ class KeyguardInteractorTest : SysuiTestCase() { repository = repository, commandQueue = commandQueue, powerInteractor = PowerInteractorFactory.create().powerInteractor, - sceneContainerFlags = kosmos.fakeSceneContainerFlags, + sceneContainerFlags = kosmos.sceneContainerFlags, bouncerRepository = bouncerRepository, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), shadeRepository = shadeRepository, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt index 78bdfb350085..5bf0f4b8dc28 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt @@ -36,6 +36,7 @@ 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 @@ -45,16 +46,19 @@ import org.junit.runner.RunWith class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() { val kosmos = testKosmos().apply { - fakeFeatureFlagsClassic.apply { - set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } + private val testScope = kosmos.testScope private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController private val underTest by lazy { kosmos.alternateBouncerToGoneTransitionViewModel } + @Before + fun setup() { + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT) + } + @Test fun deviceEntryParentViewDisappear() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt index ff41ea25f470..854a4786d059 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -41,15 +42,17 @@ import org.junit.runner.RunWith class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().apply { - fakeFeatureFlagsClassic.apply { - set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } private val underTest by lazy { kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel } + @Before + fun setup() { + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT) + } + @Test fun deviceEntryParentViewDisappear() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt index e6b30176d3ee..ee217a63e8ac 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt @@ -57,10 +57,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { private val kosmos = testKosmos().apply { - fakeFeatureFlagsClassic.apply { - set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository @@ -72,6 +69,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT) whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false) sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index fc604aa5a1d2..e3eca6729188 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -30,11 +30,8 @@ import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository -import com.android.systemui.flags.Flags -import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -60,13 +57,9 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class KeyguardRootViewModelTest : SysuiTestCase() { - private val kosmos = - testKosmos().apply { - fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) } - } + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val keyguardInteractor = kosmos.keyguardInteractor private val keyguardRepository = kosmos.fakeKeyguardRepository private val communalRepository = kosmos.communalRepository private val screenOffAnimationController = kosmos.screenOffAnimationController @@ -82,7 +75,10 @@ class KeyguardRootViewModelTest : SysuiTestCase() { fun setUp() { mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + mSetFlagsRule.disableFlags( + AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, + AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT, + ) } @Test @@ -426,4 +422,23 @@ class KeyguardRootViewModelTest : SysuiTestCase() { shadeRepository.setQsExpansion(0.5f) assertThat(alpha).isEqualTo(0f) } + + @Test + fun alpha_idleOnDream_isZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + assertThat(alpha).isEqualTo(1f) + + // Go to GONE state + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + testScope = testScope, + ) + assertThat(alpha).isEqualTo(0f) + + // Try pulling down shade and ensure the value doesn't change + shadeRepository.setQsExpansion(0.5f) + assertThat(alpha).isEqualTo(0f) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 66f7e015a133..776f1a55fdcb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -34,6 +34,8 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.shadeRepository @@ -43,6 +45,7 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificati import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat +import kotlin.math.pow import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.BeforeClass @@ -57,22 +60,26 @@ import platform.test.runner.parameterized.Parameters class LockscreenSceneViewModelTest : SysuiTestCase() { companion object { + private const val parameterCount = 6 + @Parameters( name = "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," + - " isSingleShade={3}, isCommunalAvailable={4}" + " isSingleShade={3}, isCommunalAvailable={4}, isShadeTouchable={5}" ) @JvmStatic fun combinations() = buildList { - repeat(32) { combination -> + repeat(2f.pow(parameterCount).toInt()) { combination -> add( arrayOf( - /* canSwipeToEnter= */ combination and 1 != 0, - /* downWithTwoPointers= */ combination and 2 != 0, - /* downFromEdge= */ combination and 4 != 0, - /* isSingleShade= */ combination and 8 != 0, - /* isCommunalAvailable= */ combination and 16 != 0, - ) + /* canSwipeToEnter= */ combination and 1 != 0, + /* downWithTwoPointers= */ combination and 2 != 0, + /* downFromEdge= */ combination and 4 != 0, + /* isSingleShade= */ combination and 8 != 0, + /* isCommunalAvailable= */ combination and 16 != 0, + /* isShadeTouchable= */ combination and 32 != 0, + ) + .also { check(it.size == parameterCount) } ) } } @@ -82,8 +89,15 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun setUp() { val combinationStrings = combinations().map { array -> - check(array.size == 5) - "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}" + check(array.size == parameterCount) + buildString { + ((parameterCount - 1) downTo 0).forEach { index -> + append("${array[index]}") + if (index > 0) { + append(",") + } + } + } } val uniqueCombinations = combinationStrings.toSet() assertThat(combinationStrings).hasSize(uniqueCombinations.size) @@ -92,8 +106,35 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private fun expectedDownDestination( downFromEdge: Boolean, isSingleShade: Boolean, - ): SceneKey { - return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade + isShadeTouchable: Boolean, + ): SceneKey? { + return when { + !isShadeTouchable -> null + downFromEdge && isSingleShade -> Scenes.QuickSettings + else -> Scenes.Shade + } + } + + private fun expectedUpDestination( + canSwipeToEnter: Boolean, + isShadeTouchable: Boolean, + ): SceneKey? { + return when { + !isShadeTouchable -> null + canSwipeToEnter -> Scenes.Gone + else -> Scenes.Bouncer + } + } + + private fun expectedLeftDestination( + isCommunalAvailable: Boolean, + isShadeTouchable: Boolean, + ): SceneKey? { + return when { + !isShadeTouchable -> null + isCommunalAvailable -> Scenes.Communal + else -> null + } } } @@ -106,6 +147,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @JvmField @Parameter(2) var downFromEdge: Boolean = false @JvmField @Parameter(3) var isSingleShade: Boolean = true @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false + @JvmField @Parameter(5) var isShadeTouchable: Boolean = false private val underTest by lazy { createLockscreenSceneViewModel() } @@ -130,6 +172,14 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { } ) kosmos.setCommunalAvailable(isCommunalAvailable) + kosmos.fakePowerRepository.updateWakefulness( + rawState = + if (isShadeTouchable) { + WakefulnessState.AWAKE + } else { + WakefulnessState.ASLEEP + }, + ) val destinationScenes by collectLastValue(underTest.destinationScenes) @@ -148,14 +198,25 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { expectedDownDestination( downFromEdge = downFromEdge, isSingleShade = isSingleShade, + isShadeTouchable = isShadeTouchable, ) ) assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) - .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer) + .isEqualTo( + expectedUpDestination( + canSwipeToEnter = canSwipeToEnter, + isShadeTouchable = isShadeTouchable, + ) + ) assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene) - .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable }) + .isEqualTo( + expectedLeftDestination( + isCommunalAvailable = isCommunalAvailable, + isShadeTouchable = isShadeTouchable, + ) + ) } private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index db1d5d91eb65..18e9009611c9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -44,10 +44,7 @@ import org.junit.runner.RunWith class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { val kosmos = testKosmos().apply { - fakeFeatureFlagsClassic.apply { - set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } val testScope = kosmos.testScope @@ -58,6 +55,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT) whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false) sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 9856f9050c4b..93302e32b607 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -293,6 +293,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor, deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor, + shadeInteractor = kosmos.shadeInteractor, ) startable.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index ae31058e7a54..7f7c24e6efa4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -39,7 +39,6 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) -@android.platform.test.annotations.EnabledOnRavenwood class SceneContainerRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 3fd5306abb71..75e66fb06ce8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -48,14 +48,17 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.model.sysUiState +import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory +import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor @@ -140,6 +143,7 @@ class SceneContainerStartableTest : SysuiTestCase() { occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor, deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor, + shadeInteractor = kosmos.shadeInteractor, ) } @@ -1127,6 +1131,33 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning.value).isTrue() } + @Test + fun switchToLockscreen_whenShadeBecomesNotTouchable() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable) + val transitionStateFlow = prepareState() + underTest.start() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + // Flung to bouncer, 90% of the way there: + transitionStateFlow.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Bouncer, + progress = flowOf(0.9f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + runCurrent() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + + kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.ASLEEP) + runCurrent() + assertThat(isShadeTouchable).isFalse() + + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + } + private fun TestScope.emulateSceneTransition( transitionStateFlow: MutableStateFlow<ObservableTransitionState>, toScene: SceneKey, @@ -1166,6 +1197,7 @@ class SceneContainerStartableTest : SysuiTestCase() { isLockscreenEnabled: Boolean = true, startsAwake: Boolean = true, isDeviceProvisioned: Boolean = true, + isInteractive: Boolean = true, ): MutableStateFlow<ObservableTransitionState> { if (authenticationMethod?.isSecure == true) { assert(isLockscreenEnabled) { @@ -1205,6 +1237,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } else { powerInteractor.setAsleepForTest() } + kosmos.fakePowerRepository.setInteractive(isInteractive) kosmos.fakeDeviceProvisioningRepository.setDeviceProvisioned(isDeviceProvisioned) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt new file mode 100644 index 000000000000..db31ad52e3b0 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.shared.flag + +import android.platform.test.flag.junit.FlagsParameterization +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN +import com.android.systemui.Flags.FLAG_EXAMPLE_FLAG +import com.android.systemui.Flags.FLAG_SCENE_CONTAINER +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.andSceneContainer +import com.google.common.truth.Truth +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +internal class SceneContainerFlagParameterizationTest : SysuiTestCase() { + + @Test + fun emptyAndSceneContainer() { + val result = FlagsParameterization.allCombinationsOf().andSceneContainer() + Truth.assertThat(result).hasSize(2) + Truth.assertThat(result[0].mOverrides[FLAG_SCENE_CONTAINER]).isFalse() + Truth.assertThat(result[1].mOverrides[FLAG_SCENE_CONTAINER]).isTrue() + } + + @Test + fun oneUnrelatedAndSceneContainer() { + val unrelatedFlag = FLAG_EXAMPLE_FLAG + val result = FlagsParameterization.allCombinationsOf(unrelatedFlag).andSceneContainer() + Truth.assertThat(result).hasSize(4) + Truth.assertThat(result[0].mOverrides[unrelatedFlag]).isFalse() + Truth.assertThat(result[0].mOverrides[FLAG_SCENE_CONTAINER]).isFalse() + Truth.assertThat(result[1].mOverrides[unrelatedFlag]).isFalse() + Truth.assertThat(result[1].mOverrides[FLAG_SCENE_CONTAINER]).isTrue() + Truth.assertThat(result[2].mOverrides[unrelatedFlag]).isTrue() + Truth.assertThat(result[2].mOverrides[FLAG_SCENE_CONTAINER]).isFalse() + Truth.assertThat(result[3].mOverrides[unrelatedFlag]).isTrue() + Truth.assertThat(result[3].mOverrides[FLAG_SCENE_CONTAINER]).isTrue() + } + + @Test + fun oneDependencyAndSceneContainer() { + val dependentFlag = FLAG_COMPOSE_LOCKSCREEN + val result = FlagsParameterization.allCombinationsOf(dependentFlag).andSceneContainer() + Truth.assertThat(result).hasSize(3) + Truth.assertThat(result[0].mOverrides[dependentFlag]).isFalse() + Truth.assertThat(result[0].mOverrides[FLAG_SCENE_CONTAINER]).isFalse() + Truth.assertThat(result[1].mOverrides[dependentFlag]).isTrue() + Truth.assertThat(result[1].mOverrides[FLAG_SCENE_CONTAINER]).isFalse() + Truth.assertThat(result[2].mOverrides[dependentFlag]).isTrue() + Truth.assertThat(result[2].mOverrides[FLAG_SCENE_CONTAINER]).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt index 543f6c91513e..e590bae3c53c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.kosmos.Kosmos import com.google.common.truth.Truth import org.junit.Test import org.junit.runner.RunWith @@ -35,6 +36,7 @@ internal class SceneContainerFlagsTest : SysuiTestCase() { fun isNotEnabled_withoutAconfigFlags() { Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false) Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false) + Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(false) } @Test @@ -42,5 +44,6 @@ internal class SceneContainerFlagsTest : SysuiTestCase() { fun isEnabled_withAconfigFlags() { Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true) Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true) + Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(true) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index ac8387f28e6c..8f7a56de0040 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -20,17 +20,21 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.platform.test.annotations.DisableFlags -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags +import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -47,6 +51,8 @@ import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel import com.android.systemui.kosmos.testScope import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.mockLargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor @@ -64,19 +70,36 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class SharedNotificationContainerViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +// SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on +@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) +class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf( + FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, + ) + .andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags!!) + } + val aodBurnInViewModel = mock(AodBurnInViewModel::class.java) lateinit var movementFlow: MutableStateFlow<BurnInModel> val kosmos = testKosmos().apply { - fakeFeatureFlagsClassic.apply { - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) - } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } init { @@ -84,19 +107,28 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } val testScope = kosmos.testScope - val configurationRepository = kosmos.fakeConfigurationRepository - val keyguardRepository = kosmos.fakeKeyguardRepository - val keyguardInteractor = kosmos.keyguardInteractor - val keyguardRootViewModel = kosmos.keyguardRootViewModel - val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - val shadeRepository = kosmos.shadeRepository - val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor - val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper + val configurationRepository + get() = kosmos.fakeConfigurationRepository + val keyguardRepository + get() = kosmos.fakeKeyguardRepository + val keyguardInteractor + get() = kosmos.keyguardInteractor + val keyguardRootViewModel + get() = kosmos.keyguardRootViewModel + val keyguardTransitionRepository + get() = kosmos.fakeKeyguardTransitionRepository + val shadeRepository + get() = kosmos.shadeRepository + val sharedNotificationContainerInteractor + get() = kosmos.sharedNotificationContainerInteractor + val largeScreenHeaderHelper + get() = kosmos.mockLargeScreenHeaderHelper lateinit var underTest: SharedNotificationContainerViewModel @Before fun setUp() { + assertThat(kosmos.sceneContainerFlags.isEnabled()).isEqualTo(SceneContainerFlag.isEnabled) overrideResource(R.bool.config_use_split_notification_shade, false) movementFlow = MutableStateFlow(BurnInModel()) whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow) @@ -130,9 +162,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() = testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.bool.config_use_large_screen_shade_header, true) @@ -148,9 +180,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) overrideResource(R.bool.config_use_split_notification_shade, true) overrideResource(R.bool.config_use_large_screen_shade_header, true) @@ -243,9 +275,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test @DisableFlags(FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val headerResourceHeight = 50 val headerHelperHeight = 100 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) @@ -263,9 +295,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test @EnableSceneContainer + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun validateMarginTopWithLargeScreenHeader_sceneContainerFlagOn_stillZero() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val headerResourceHeight = 50 val headerHelperHeight = 100 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) @@ -282,6 +314,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(bugId = 333132830) fun glanceableHubAlpha_lockscreenToHub() = testScope.runTest { val alpha by collectLastValue(underTest.glanceableHubAlpha) @@ -431,6 +464,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(bugId = 333132830) fun isOnLockscreenWithoutShade() = testScope.runTest { val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade) @@ -467,6 +501,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(bugId = 333132830) fun isOnGlanceableHubWithoutShade() = testScope.runTest { val isOnGlanceableHubWithoutShade by @@ -503,6 +538,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun boundsOnLockscreenNotInSplitShade() = testScope.runTest { val bounds by collectLastValue(underTest.bounds) @@ -523,9 +559,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, FLAG_SCENE_CONTAINER) fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() = testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val bounds by collectLastValue(underTest.bounds) // When in split shade @@ -547,13 +583,20 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { runCurrent() // Top should be equal to bounds (1) - padding adjustment (10) - assertThat(bounds).isEqualTo(NotificationContainerBounds(top = -9f, bottom = 2f)) + assertThat(bounds) + .isEqualTo( + NotificationContainerBounds( + top = -9f, + bottom = 2f, + ) + ) } @Test + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) + @DisableFlags(FLAG_SCENE_CONTAINER) fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val bounds by collectLastValue(underTest.bounds) // When in split shade @@ -579,6 +622,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun boundsOnShade() = testScope.runTest { val bounds by collectLastValue(underTest.bounds) @@ -594,6 +638,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun boundsOnQS() = testScope.runTest { val bounds by collectLastValue(underTest.bounds) @@ -638,6 +683,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(bugId = 333132830) fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() = testScope.runTest { var notificationCount = 10 @@ -674,6 +720,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(bugId = 333132830) fun maxNotificationsOnShade() = testScope.runTest { val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 } @@ -693,6 +740,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun translationYUpdatesOnKeyguardForBurnIn() = testScope.runTest { val translationY by collectLastValue(underTest.translationY(BurnInParameters())) @@ -726,6 +774,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun translationYDoesNotUpdateWhenShadeIsExpanded() = testScope.runTest { val translationY by collectLastValue(underTest.translationY(BurnInParameters())) @@ -746,6 +795,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SCENE_CONTAINER) fun updateBounds_fromKeyguardRoot() = testScope.runTest { val bounds by collectLastValue(underTest.bounds) @@ -757,6 +807,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(bugId = 333132830) fun alphaOnFullQsExpansion() = testScope.runTest { val viewState = ViewStateAccessor() @@ -864,6 +915,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(bugId = 333132830) fun shadeCollapseFadeIn() = testScope.runTest { val fadeIn by collectValues(underTest.shadeCollapseFadeIn) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt index e31cdcd82e7e..dc9613904e4e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt @@ -72,7 +72,7 @@ class AncSliceRepositoryTest : SysuiTestCase() { testScope.runTest { localMediaRepository.updateCurrentConnectedDevice(null) - val slice by collectLastValue(underTest.ancSlice(1)) + val slice by collectLastValue(underTest.ancSlice(1, false, false)) runCurrent() assertThat(slice).isNull() @@ -86,7 +86,7 @@ class AncSliceRepositoryTest : SysuiTestCase() { testScope.runTest { localMediaRepository.updateCurrentConnectedDevice(createMediaDevice()) - val slice by collectLastValue(underTest.ancSlice(1)) + val slice by collectLastValue(underTest.ancSlice(1, false, false)) runCurrent() assertThat(slice).isNotNull() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt index 53f0bc9ddb51..81e6ac412404 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.volume.panel.component.anc.FakeSliceFactory import com.android.systemui.volume.panel.component.anc.ancSliceRepository +import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -57,10 +58,10 @@ class AncSliceInteractorTest : SysuiTestCase() { FakeSliceFactory.createSlice(hasError = true, hasSliceItem = true) ) - val slice by collectLastValue(underTest.ancSlice) + val slice by collectLastValue(underTest.ancSlices) runCurrent() - assertThat(slice).isNull() + assertThat(slice).isInstanceOf(AncSlices.Unavailable::class.java) } } } @@ -74,10 +75,10 @@ class AncSliceInteractorTest : SysuiTestCase() { FakeSliceFactory.createSlice(hasError = false, hasSliceItem = false) ) - val slice by collectLastValue(underTest.ancSlice) + val slice by collectLastValue(underTest.ancSlices) runCurrent() - assertThat(slice).isNull() + assertThat(slice).isInstanceOf(AncSlices.Unavailable::class.java) } } } @@ -91,10 +92,10 @@ class AncSliceInteractorTest : SysuiTestCase() { FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true) ) - val slice by collectLastValue(underTest.ancSlice) + val slice by collectLastValue(underTest.ancSlices) runCurrent() - assertThat(slice).isNotNull() + assertThat(slice).isInstanceOf(AncSlices.Ready::class.java) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt deleted file mode 100644 index 79d3fe9063b7..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.panel.component.volume.domain.interactor - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -@SmallTest -class VolumeSliderInteractorTest : SysuiTestCase() { - - private val underTest = VolumeSliderInteractor() - - @Test - fun processVolumeToValue_returnsTranslatedVolume() { - assertThat(underTest.processVolumeToValue(2, volumeRange)).isEqualTo(20f) - } - - private companion object { - val volumeRange = 0..10 - } -} diff --git a/packages/SystemUI/res/drawable/ic_noise_aware.xml b/packages/SystemUI/res/drawable/ic_noise_aware.xml deleted file mode 100644 index 54826414f3f7..000000000000 --- a/packages/SystemUI/res/drawable/ic_noise_aware.xml +++ /dev/null @@ -1,26 +0,0 @@ -<!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="960" - android:viewportHeight="960" - android:tint="?attr/colorControlNormal"> - <path - android:fillColor="@android:color/white" - android:pathData="M440,82Q450,81 460,80.5Q470,80 480,80Q491,80 500.5,80.5Q510,81 520,82L520,162Q510,160 500.5,160Q491,160 480,160Q469,160 459.5,160Q450,160 440,162L440,82ZM272,138Q289,127 306.5,119Q324,111 343,104L378,176Q358,182 340.5,190.5Q323,199 306,210L272,138ZM654,210Q637,199 619.5,190.5Q602,182 582,176L617,104Q636,111 653.5,119Q671,127 688,138L654,210ZM753,311Q742,294 729,278.5Q716,263 702,249L765,199Q779,213 792,228.5Q805,244 816,261L753,311ZM143,263Q154,246 166.5,230.5Q179,215 193,201L256,251Q242,265 229.5,280.5Q217,296 206,313L143,263ZM83,428Q85,408 90,388.5Q95,369 101,350L180,368Q173,387 168.5,406.5Q164,426 162,446L83,428ZM799,449Q797,429 792.5,409Q788,389 781,370L859,352Q865,371 870,390.5Q875,410 877,430L799,449ZM781,590Q788,571 792,552Q796,533 798,513L877,531Q875,551 870,570.5Q865,590 859,609L781,590ZM162,514Q164,534 168.5,553.5Q173,573 180,592L101,610Q95,591 90,571.5Q85,552 83,532L162,514ZM705,708Q719,694 731,678.5Q743,663 754,646L818,696Q807,713 794.5,728.5Q782,744 768,758L705,708ZM194,760Q180,746 167.5,730Q155,714 144,697L206,647Q217,664 229.5,680Q242,696 256,710L194,760ZM583,783Q603,776 620,768Q637,760 654,749L689,821Q672,832 654.5,840.5Q637,849 618,856L583,783ZM344,857Q325,850 307,841.5Q289,833 272,822L307,750Q324,761 341.5,769.5Q359,778 379,784L344,857ZM480,880Q470,880 460,879.5Q450,879 440,878L440,798Q453,800 480,800Q491,800 500.5,800Q510,800 520,798L520,878Q510,879 500.5,879.5Q491,880 480,880ZM520,720Q482,720 450.5,697Q419,674 406,638Q403,629 399.5,620.5Q396,612 389,605L334,550Q308,524 294,490.5Q280,457 280,420Q280,345 332.5,292.5Q385,240 460,240Q529,240 580,285.5Q631,331 639,400L558,400Q551,365 523.5,342.5Q496,320 460,320Q418,320 389,349Q360,378 360,420Q360,440 368,459.5Q376,479 391,494L445,548Q459,562 467.5,578.5Q476,595 482,612Q487,625 497,632.5Q507,640 520,640Q537,640 548.5,628.5Q560,617 560,600L640,600Q640,650 605.5,685Q571,720 520,720ZM540,560Q515,560 497.5,542.5Q480,525 480,500Q480,474 497.5,457Q515,440 540,440Q566,440 583,457Q600,474 600,500Q600,525 583,542.5Q566,560 540,560Z"/> -</vector> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b0b548295b71..af327d2b7791 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -2001,4 +2001,7 @@ <!-- SliceView grid gutter for ANC Slice --> <dimen name="abc_slice_grid_gutter">0dp</dimen> + <!-- SliceView icon size --> + <dimen name="abc_slice_big_pic_min_height">64dp</dimen> + <dimen name="abc_slice_big_pic_max_height">64dp</dimen> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index eca932c531b2..3b8a268cd5ea 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -31,7 +31,6 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.customization.R import com.android.systemui.dagger.qualifiers.Background @@ -66,7 +65,6 @@ import java.util.Locale import java.util.TimeZone import java.util.concurrent.Executor import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job @@ -74,6 +72,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.launch /** * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by @@ -91,7 +90,6 @@ constructor( @DisplaySpecific private val resources: Resources, private val context: Context, @Main private val mainExecutor: DelayableExecutor, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, @Background private val bgExecutor: Executor, private val clockBuffers: ClockMessageBuffers, private val featureFlags: FeatureFlagsClassic, @@ -426,13 +424,12 @@ constructor( keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) zenModeController.addCallback(zenModeCallback) disposableHandle = - parent.repeatWhenAttached(mainImmediateDispatcher) { + parent.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { listenForDozing(this) if (MigrateClocksToBlueprint.isEnabled) { listenForDozeAmountTransition(this) listenForAnyStateToAodTransition(this) - listenForAnyStateToLockscreenTransition(this) } else { listenForDozeAmount(this) } @@ -532,14 +529,12 @@ constructor( @VisibleForTesting internal fun listenForDozeAmount(scope: CoroutineScope): Job { - return scope.launch("$TAG#listenForDozeAmount") { - keyguardInteractor.dozeAmount.collect { handleDoze(it) } - } + return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } } } @VisibleForTesting internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { - return scope.launch("$TAG#listenForDozeAmountTransition") { + return scope.launch { merge( keyguardTransitionInteractor.aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) @@ -557,7 +552,7 @@ constructor( */ @VisibleForTesting internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job { - return scope.launch("$TAG#listenForAnyStateToAodTransition") { + return scope.launch { keyguardTransitionInteractor .transitionStepsToState(AOD) .filter { it.transitionState == TransitionState.STARTED } @@ -567,19 +562,8 @@ constructor( } @VisibleForTesting - internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job { - return scope.launch("$TAG#listenForAnyStateToLockscreenTransition") { - keyguardTransitionInteractor - .transitionStepsToState(LOCKSCREEN) - .filter { it.transitionState == TransitionState.STARTED } - .filter { it.from != AOD } - .collect { handleDoze(0f) } - } - } - - @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { - return scope.launch("$TAG#listenForDozing") { + return scope.launch { combine( keyguardInteractor.dozeAmount, keyguardInteractor.isDozing, @@ -644,7 +628,7 @@ constructor( } companion object { - private const val TAG = "ClockEventController" - private const val DOZE_TICKRATE_THRESHOLD = 0.99f + private val TAG = ClockEventController::class.simpleName!! + private val DOZE_TICKRATE_THRESHOLD = 0.99f } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 25d771308aea..c509356c4e63 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -83,9 +83,9 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardWmStateRefactor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -101,6 +101,8 @@ import com.android.systemui.util.ViewController; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.GlobalSettings; +import dagger.Lazy; + import java.io.File; import java.util.Arrays; import java.util.Optional; @@ -108,7 +110,6 @@ import java.util.Optional; import javax.inject.Inject; import javax.inject.Provider; -import dagger.Lazy; import kotlinx.coroutines.Job; /** Controller for {@link KeyguardSecurityContainer} */ @@ -310,7 +311,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard */ @Override public void finish(int targetUserId) { - if (!mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (!RefactorKeyguardDismissIntent.isEnabled()) { // If there's a pending runnable because the user interacted with a widget // and we're leaving keyguard, then run it. boolean deferKeyguardDone = false; @@ -649,7 +650,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard * @param action callback to be invoked when keyguard disappear animation completes. */ public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) { - if (mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (RefactorKeyguardDismissIntent.isEnabled()) { return; } if (mCancelAction != null) { @@ -943,7 +944,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mUiEventLogger.log(uiEvent, getSessionId()); } - if (mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (RefactorKeyguardDismissIntent.isEnabled()) { if (authenticatedWithPrimaryAuth) { mPrimaryBouncerInteractor.get() .notifyKeyguardAuthenticatedPrimaryAuth(targetUserId); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8c51a4e0ce66..4987724ea7b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -225,7 +225,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected static final int BIOMETRIC_STATE_STOPPED = 0; /** Biometric authentication state: Listening. */ - private static final int BIOMETRIC_STATE_RUNNING = 1; + protected static final int BIOMETRIC_STATE_RUNNING = 1; /** * Biometric authentication: Cancelling and waiting for the relevant biometric service to @@ -1145,7 +1145,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (getUserCanSkipBouncer(userId)) { mTrustManager.unlockedByBiometricForUser(userId, FACE); } - updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); mLogger.d("onFaceAuthenticated"); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -1156,6 +1155,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + // Intentionally update the fingerprint running state after sending the + // onBiometricAuthenticated callback to listeners. Updating the fingerprint listening state + // can update the state of the device which listeners to the callback may rely on. + // For example, the alternate bouncer visibility state or udfps finger down state. + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); + // Only authenticate face once when assistant is visible mAssistantVisible = false; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 3a45db17b64c..61d1c713fb77 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -323,7 +323,13 @@ class UdfpsControllerOverlay @JvmOverloads constructor( overlayParams = updatedOverlayParams sensorBounds = updatedOverlayParams.sensorBounds getTouchOverlay()?.let { - windowManager.updateViewLayout(it, coreLayoutParams.updateDimensions(null)) + if (addViewRunnable != null) { + // Only updateViewLayout if there's no pending view to add to WM. + // If there is a pending view, that means the view hasn't been added yet so there's + // no need to update any layouts. Instead the correct params will be used when the + // view is eventually added. + windowManager.updateViewLayout(it, coreLayoutParams.updateDimensions(null)) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt index 83b3380ae6be..1eef91debab3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt @@ -27,7 +27,8 @@ import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject -private val SUPPORTED_ROTATIONS = setOf(Surface.ROTATION_90, Surface.ROTATION_270) +private val SUPPORTED_ROTATIONS = + setOf(Surface.ROTATION_90, Surface.ROTATION_270, Surface.ROTATION_180) /** * TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations. 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 76d46ed9889f..072fe47df42b 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 @@ -463,6 +463,23 @@ object BiometricViewBinder { } } } + + // Retry and confirmation when finger on sensor + launch { + combine( + viewModel.canTryAgainNow, + viewModel.hasFingerOnSensor, + viewModel.isPendingConfirmation, + ::Triple + ) + .collect { (canRetry, fingerAcquired, pendingConfirmation) -> + if (canRetry && fingerAcquired) { + legacyCallback.onButtonTryAgain() + } else if (pendingConfirmation && fingerAcquired) { + viewModel.confirmAuthenticated() + } + } + } } } 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 21ebff4d0b71..4e9acbd25b62 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 @@ -22,6 +22,7 @@ import android.content.pm.PackageManager import android.graphics.Rect import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.biometrics.PromptContentView @@ -32,6 +33,7 @@ import com.android.systemui.Flags.bpTalkback import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor @@ -40,6 +42,7 @@ import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.Job @@ -66,6 +69,7 @@ constructor( promptSelectorInteractor: PromptSelectorInteractor, @Application private val context: Context, private val udfpsOverlayInteractor: UdfpsOverlayInteractor, + private val biometricStatusInteractor: BiometricStatusInteractor, private val udfpsUtils: UdfpsUtils ) { /** The set of modalities available for this prompt */ @@ -185,6 +189,24 @@ constructor( /** Fingerprint sensor state. */ val fingerprintStartMode: Flow<FingerprintStartMode> = _fingerprintStartMode.asStateFlow() + /** Whether a finger has been acquired by the sensor */ + // TODO(b/331948073): Add support for detecting SFPS finger without authentication running + val hasFingerBeenAcquired: Flow<Boolean> = + combine(biometricStatusInteractor.fingerprintAcquiredStatus, modalities) { + status, + modalities -> + modalities.hasSfps && + status is AcquiredFingerprintAuthenticationStatus && + status.acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START + } + .distinctUntilChanged() + + /** Whether there is currently a finger on the sensor */ + val hasFingerOnSensor: Flow<Boolean> = + combine(hasFingerBeenAcquired, _isOverlayTouched) { hasFingerBeenAcquired, overlayTouched -> + hasFingerBeenAcquired || overlayTouched + } + private val _forceLargeSize = MutableStateFlow(false) private val _forceMediumSize = MutableStateFlow(false) diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 4d328d6cb13f..5a174b9d2f80 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -21,6 +21,7 @@ import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -70,9 +71,9 @@ constructor( keyguardTransitionInteractor.startedKeyguardTransitionStep .mapLatest(::determineSceneAfterTransition) .filterNotNull() - // TODO(b/322787129): Also set a custom transition animation here to avoid the regular - // slide-in animation when setting the scene programmatically - .onEach { nextScene -> communalInteractor.changeScene(nextScene) } + .onEach { nextScene -> + communalInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade) + } .launchIn(applicationScope) // TODO(b/322787129): re-enable once custom animations are in place @@ -143,7 +144,14 @@ constructor( val docked = dockManager.isDocked return when { - docked && to == KeyguardState.LOCKSCREEN && from == KeyguardState.DREAMING -> { + to == KeyguardState.OCCLUDED -> { + // Hide communal when an activity is started on keyguard, to ensure the activity + // underneath the hub is shown. + CommunalScenes.Blank + } + to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> { + // When transitioning to the hub from an occluded state, fade out the hub without + // doing any translation. CommunalScenes.Communal } to == KeyguardState.GONE -> CommunalScenes.Blank diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index c724244816ea..9debe0e56083 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -57,6 +57,9 @@ interface CommunalSettingsRepository { * Settings. */ fun getWidgetCategories(user: UserInfo): Flow<CommunalWidgetCategories> + + /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */ + fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> } @SysUISingleton @@ -115,6 +118,16 @@ constructor( } .flowOn(bgDispatcher) + override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = + broadcastDispatcher + .broadcastFlow( + filter = + IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + user = user.userHandle + ) + .emitOnStart() + .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) } + private fun getEnabledByUser(user: UserInfo): Flow<Boolean> = secureSettings .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED)) @@ -128,16 +141,6 @@ constructor( ) == 1 } - private fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = - broadcastDispatcher - .broadcastFlow( - filter = - IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), - user = user.userHandle - ) - .emitOnStart() - .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) } - companion object { const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting" private const val ENABLED_SETTING_DEFAULT = 1 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 246d5d92f8b0..373e1c9daa7b 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 @@ -44,6 +44,8 @@ import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dock.DockManager +import com.android.systemui.dock.retrieveIsDocked import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger @@ -97,12 +99,13 @@ constructor( mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, keyguardInteractor: KeyguardInteractor, - communalSettingsInteractor: CommunalSettingsInteractor, + private val communalSettingsInteractor: CommunalSettingsInteractor, private val appWidgetHost: CommunalAppWidgetHost, private val editWidgetsActivityStarter: EditWidgetsActivityStarter, private val userTracker: UserTracker, private val activityStarter: ActivityStarter, private val userManager: UserManager, + private val dockManager: DockManager, sceneInteractor: SceneInteractor, sceneContainerFlags: SceneContainerFlags, @CommunalLog logBuffer: LogBuffer, @@ -123,7 +126,7 @@ constructor( and( communalSettingsInteractor.isCommunalEnabled, not(keyguardInteractor.isEncryptedOrLockdown), - or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming) + or(keyguardInteractor.isKeyguardShowing, keyguardInteractor.isDreaming) ) .distinctUntilChanged() .onEach { available -> @@ -143,6 +146,9 @@ constructor( replay = 1, ) + /** Whether to show communal by default */ + val showByDefault: Flow<Boolean> = and(isCommunalAvailable, dockManager.retrieveIsDocked()) + /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. * @@ -352,7 +358,14 @@ constructor( /** A list of widget content to be displayed in the communal hub. */ val widgetContent: Flow<List<WidgetContent>> = combine( - widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) }, + widgetRepository.communalWidgets + .map { filterWidgetsByExistingUsers(it) } + .combine(communalSettingsInteractor.allowedByDevicePolicyForWorkProfile) { + // exclude widgets under work profile if not allowed by device policy + widgets, + allowedForWorkProfile -> + filterWidgetsAllowedByDevicePolicy(widgets, allowedForWorkProfile) + }, communalSettingsInteractor.communalWidgetCategories, updateOnWorkProfileBroadcastReceived, ) { widgets, allowedCategories, _ -> @@ -374,6 +387,19 @@ constructor( } } + /** Filter widgets based on whether their associated profile is allowed by device policy. */ + private fun filterWidgetsAllowedByDevicePolicy( + list: List<CommunalWidgetContentModel>, + allowedByDevicePolicyForWorkProfile: Boolean + ): List<CommunalWidgetContentModel> = + if (allowedByDevicePolicyForWorkProfile) { + list + } else { + // Get associated work profile for the currently selected user. + val workProfile = userTracker.userProfiles.find { it.isManagedProfile } + list.filter { it.providerInfo.profile.identifier != workProfile?.id } + } + /** A flow of available smartspace targets. Currently only showing timers. */ private val smartspaceTargets: Flow<List<SmartspaceTarget>> = if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index 20f60b79c784..f9de60984e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -16,6 +16,8 @@ package com.android.systemui.communal.domain.interactor +import android.content.pm.UserInfo +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.communal.data.model.CommunalEnabledState import com.android.systemui.communal.data.model.CommunalWidgetCategories import com.android.systemui.communal.data.repository.CommunalSettingsRepository @@ -24,13 +26,18 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.dagger.CommunalTableLog import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.settings.UserTracker import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -40,8 +47,10 @@ class CommunalSettingsInteractor @Inject constructor( @Background private val bgScope: CoroutineScope, + @Background private val bgExecutor: Executor, private val repository: CommunalSettingsRepository, userInteractor: SelectedUserInteractor, + private val userTracker: UserTracker, @CommunalTableLog tableLogBuffer: TableLogBuffer, ) { /** Whether or not communal is enabled for the currently selected user. */ @@ -68,4 +77,33 @@ constructor( started = SharingStarted.Eagerly, initialValue = CommunalWidgetCategories().categories ) + + private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow { + fun send(profiles: List<UserInfo>) { + trySend(profiles.find { it.isManagedProfile }) + } + + val callback = + object : UserTracker.Callback { + override fun onProfilesChanged(profiles: List<UserInfo>) { + send(profiles) + } + } + userTracker.addCallback(callback, bgExecutor) + send(userTracker.userProfiles) + + awaitClose { userTracker.removeCallback(callback) } + } + + /** Whether or not keyguard widgets are allowed for work profile by device policy manager. */ + val allowedByDevicePolicyForWorkProfile: StateFlow<Boolean> = + workProfileUserInfoCallbackFlow + .flatMapLatest { workProfile -> + workProfile?.let { repository.getAllowedByDevicePolicy(it) } ?: flowOf(false) + } + .stateIn( + scope = bgScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false + ) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt new file mode 100644 index 000000000000..a3c61a413639 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalTransitionKeys.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.shared.model + +import com.android.compose.animation.scene.TransitionKey + +/** + * Defines all known named transitions for [CommunalScenes]. + * + * These transitions can be referenced by key when changing scenes programmatically. + */ +object CommunalTransitionKeys { + /** Fades the glanceable hub without any translation */ + val SimpleFade = TransitionKey("SimpleFade") +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt index 96e4b341cb6d..bdf4e721a551 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -16,7 +16,11 @@ package com.android.systemui.communal.ui.viewmodel +import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel @@ -25,6 +29,7 @@ import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.merge /** View model for transitions related to the communal hub. */ @@ -37,6 +42,8 @@ constructor( lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel, dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel, + communalInteractor: CommunalInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /** * Whether UMO location should be on communal. This flow is responsive to transitions so that a @@ -51,4 +58,14 @@ constructor( glanceableHubToDreamTransitionViewModel.showUmo, ) .distinctUntilChanged() + + /** Whether to show communal by default */ + val showByDefault: Flow<Boolean> = communalInteractor.showByDefault + + val transitionFromOccludedEnded = + keyguardTransitionInteractor.transitionStepsFromState(KeyguardState.OCCLUDED).filter { step + -> + step.transitionState == TransitionState.FINISHED || + step.transitionState == TransitionState.CANCELED + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt index 0e04d15d8680..baae986c494d 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt @@ -40,6 +40,7 @@ import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationSta import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.BiometricType import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository @@ -312,7 +313,11 @@ constructor( // or device starts going to sleep. merge( powerInteractor.isAsleep, - keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE), + if (KeyguardWmStateRefactor.isEnabled) { + keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE) + } else { + keyguardRepository.keyguardDoneAnimationsFinished.map { true } + }, userRepository.selectedUser.map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS }, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index 1a855d735a02..95012a2643a5 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -54,8 +54,6 @@ public class DozeUi implements DozeMachine.Part { private final DozeParameters mDozeParameters; private final DozeLog mDozeLog; private final DelayableExecutor mBgExecutor; - - private Runnable mCancelRunnable = null; private long mLastTimeTickElapsed = 0; // If time tick is scheduled and there's not a pending runnable to cancel: private volatile boolean mTimeTickScheduled; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt index 037c23b579c3..ac03463da545 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel @@ -28,6 +29,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel import com.android.systemui.res.R import com.android.systemui.settings.UserTracker +import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -49,7 +51,8 @@ constructor( private val communalInteractor: CommunalInteractor, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val userTracker: UserTracker, -) { + dumpManager: DumpManager, +) : FlowDumperImpl(dumpManager) { fun startTransitionFromDream() { val showGlanceableHub = @@ -83,6 +86,7 @@ constructor( toGlanceableHubTransitionViewModel.dreamAlpha, ) .distinctUntilChanged() + .dumpWhileCollecting("dreamAlpha") val dreamOverlayAlpha: Flow<Float> = merge( diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 640534cc9d34..612ae6c875f4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -153,11 +153,6 @@ object Flags { // TODO(b/267722622): Tracking Bug @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp") - /** Whether to use a new data source for intents to run on keyguard dismissal. */ - // TODO(b/275069969): Tracking bug. - @JvmField - val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag("refactor_keyguard_dismiss_intent") - /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */ // TODO(b/277220285): Tracking bug. @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index e6e6ff6dcadb..c32c226441fe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -41,7 +41,6 @@ import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor @@ -73,7 +72,6 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import dagger.Lazy import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -110,7 +108,6 @@ constructor( private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder, private val clockInteractor: KeyguardClockInteractor, private val keyguardViewMediator: KeyguardViewMediator, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -214,7 +211,6 @@ constructor( vibratorHelper, falsingManager, keyguardViewMediator, - mainImmediateDispatcher, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 654610e8cae8..2a9dad0162c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -137,6 +137,7 @@ import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.TransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; @@ -584,7 +585,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private CentralSurfaces mCentralSurfaces; - private IRemoteAnimationFinishedCallback mUnoccludeFromDreamFinishedCallback; + private IRemoteAnimationFinishedCallback mUnoccludeFinishedCallback; private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @@ -1234,10 +1235,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mUnoccludeAnimator.cancel(); } - if (isDream) { + if (isDream || mShowCommunalByDefault) { initAlphaForAnimationTargets(wallpapers); - mDreamViewModel.get().startTransitionFromDream(); - mUnoccludeFromDreamFinishedCallback = finishedCallback; + if (isDream) { + mDreamViewModel.get().startTransitionFromDream(); + } + mUnoccludeFinishedCallback = finishedCallback; return; } @@ -1304,7 +1307,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Consumer<Float> getRemoteSurfaceAlphaApplier() { return (Float alpha) -> { - if (mRemoteAnimationTarget == null) return; + if (mRemoteAnimationTarget == null) { + Log.e(TAG, "Attempting to set alpha on null animation target"); + return; + } final View localView = mKeyguardViewControllerLazy.get().getViewRootImpl().getView(); final SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier(localView); @@ -1319,10 +1325,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Consumer<TransitionStep> getFinishedCallbackConsumer() { return (TransitionStep step) -> { - if (mUnoccludeFromDreamFinishedCallback == null) return; + if (mUnoccludeFinishedCallback == null) return; try { - mUnoccludeFromDreamFinishedCallback.onAnimationFinished(); - mUnoccludeFromDreamFinishedCallback = null; + mUnoccludeFinishedCallback.onAnimationFinished(); + mUnoccludeFinishedCallback = null; } catch (RemoteException e) { Log.e(TAG, "Wasn't able to callback", e); } @@ -1365,7 +1371,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final SessionTracker mSessionTracker; private final CoroutineDispatcher mMainDispatcher; private final Lazy<DreamViewModel> mDreamViewModel; + private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel; private RemoteAnimationTarget mRemoteAnimationTarget; + private Boolean mShowCommunalByDefault; private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager; @@ -1414,6 +1422,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, SystemClock systemClock, @Main CoroutineDispatcher mainDispatcher, Lazy<DreamViewModel> dreamViewModel, + Lazy<CommunalTransitionViewModel> communalTransitionViewModel, SystemPropertiesHelper systemPropertiesHelper, Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, SelectedUserInteractor selectedUserInteractor, @@ -1485,6 +1494,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mSessionTracker = sessionTracker; mDreamViewModel = dreamViewModel; + mCommunalTransitionViewModel = communalTransitionViewModel; mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager; mMainDispatcher = mainDispatcher; @@ -1615,11 +1625,21 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl(); if (viewRootImpl != null) { - final DreamViewModel viewModel = mDreamViewModel.get(); - collectFlow(viewRootImpl.getView(), viewModel.getDreamAlpha(), + final DreamViewModel dreamViewModel = mDreamViewModel.get(); + final CommunalTransitionViewModel communalViewModel = + mCommunalTransitionViewModel.get(); + collectFlow(viewRootImpl.getView(), dreamViewModel.getDreamAlpha(), getRemoteSurfaceAlphaApplier(), mMainDispatcher); - collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(), + collectFlow(viewRootImpl.getView(), dreamViewModel.getTransitionEnded(), getFinishedCallbackConsumer(), mMainDispatcher); + collectFlow(viewRootImpl.getView(), communalViewModel.getShowByDefault(), + (showByDefault) -> + mShowCommunalByDefault = showByDefault, mMainDispatcher); + collectFlow(viewRootImpl.getView(), + communalViewModel.getTransitionFromOccludedEnded(), + getFinishedCallbackConsumer(), mMainDispatcher); + } else { + Log.e(TAG, "Keyguard ViewRootImpl is null"); } } // Most services aren't available until the system reaches the ready state, so we 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 a243b8eec264..7879ab6e1ed2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -39,6 +39,7 @@ import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingModule; +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; @@ -161,6 +162,7 @@ public interface KeyguardModule { SystemClock systemClock, @Main CoroutineDispatcher mainDispatcher, Lazy<DreamViewModel> dreamViewModel, + Lazy<CommunalTransitionViewModel> communalTransitionViewModel, SystemPropertiesHelper systemPropertiesHelper, Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, SelectedUserInteractor selectedUserInteractor, @@ -208,6 +210,7 @@ public interface KeyguardModule { systemClock, mainDispatcher, dreamViewModel, + communalTransitionViewModel, systemPropertiesHelper, wmLockscreenVisibilityManager, selectedUserInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt index 3f4d3a8544d0..6c29bce616bc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.data.repository +import android.content.Context import android.os.UserHandle import android.provider.Settings import com.android.keyguard.ClockEventController @@ -24,9 +25,12 @@ import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.SettingsClockSize import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockId +import com.android.systemui.res.R import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow @@ -47,7 +51,11 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext interface KeyguardClockRepository { - /** clock size determined by notificationPanelViewController, LARGE or SMALL */ + /** + * clock size determined by notificationPanelViewController, LARGE or SMALL + * + * @deprecated When scene container flag is on use clockSize from domain level. + */ val clockSize: StateFlow<Int> /** clock size selected in picker, DYNAMIC or SMALL */ @@ -61,6 +69,9 @@ interface KeyguardClockRepository { val previewClock: Flow<ClockController> val clockEventController: ClockEventController + + val shouldForceSmallClock: Boolean + fun setClockSize(@ClockSize size: Int) } @@ -73,6 +84,8 @@ constructor( override val clockEventController: ClockEventController, @Background private val backgroundDispatcher: CoroutineDispatcher, @Application private val applicationScope: CoroutineScope, + @Application private val applicationContext: Context, + private val featureFlags: FeatureFlagsClassic, ) : KeyguardClockRepository { /** Receive SMALL or LARGE clock should be displayed on keyguard. */ @@ -135,6 +148,12 @@ constructor( clockRegistry.createCurrentClock() } + override val shouldForceSmallClock: Boolean + get() = + featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) && + // True on small landscape screens + applicationContext.resources.getBoolean(R.bool.force_small_clock_on_lockscreen) + private fun getClockSize(): SettingsClockSize { return if ( secureSettings.getIntForUser( 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 1298fa5af033..462d8373a430 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 @@ -206,7 +206,11 @@ interface KeyguardRepository { ) val keyguardDoneAnimationsFinished: Flow<Unit> - /** Receive whether clock should be centered on lockscreen. */ + /** + * Receive whether clock should be centered on lockscreen. + * + * @deprecated When scene container flag is on use clockShouldBeCentered from domain level. + */ val clockShouldBeCentered: Flow<Boolean> /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index b6289d44e2c3..ee589f4a235c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -87,12 +87,12 @@ constructor( scope.launch { keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop .filterRelevantKeyguardStateAnd { onTop -> !onTop } - .sample(communalInteractor.isIdleOnCommunal, ::Pair) - .collect { (_, isIdleOnCommunal) -> + .sample(communalInteractor.isIdleOnCommunal, communalInteractor.showByDefault) + .collect { (_, isIdleOnCommunal, showCommunalByDefault) -> // Occlusion signals come from the framework, and should interrupt any // existing transition val to = - if (isIdleOnCommunal) { + if (isIdleOnCommunal || showCommunalByDefault) { KeyguardState.GLANCEABLE_HUB } else { KeyguardState.LOCKSCREEN @@ -106,15 +106,16 @@ constructor( .sample( keyguardInteractor.isKeyguardShowing, communalInteractor.isIdleOnCommunal, + communalInteractor.showByDefault, ) - .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _) -> + .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) -> !isOccluded && isShowing } - .collect { (_, _, isIdleOnCommunal) -> + .collect { (_, _, isIdleOnCommunal, showCommunalByDefault) -> // Occlusion signals come from the framework, and should interrupt any // existing transition val to = - if (isIdleOnCommunal) { + if (isIdleOnCommunal || showCommunalByDefault) { KeyguardState.GLANCEABLE_HUB } else { KeyguardState.LOCKSCREEN @@ -175,6 +176,7 @@ constructor( duration = when (toState) { KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION + KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds } @@ -184,6 +186,7 @@ constructor( const val TAG = "FromOccludedTransitionInteractor" private val DEFAULT_DURATION = 500.milliseconds val TO_LOCKSCREEN_DURATION = 933.milliseconds + val TO_GLANCEABLE_HUB_DURATION = 250.milliseconds val TO_AOD_DURATION = DEFAULT_DURATION val TO_DOZING_DURATION = DEFAULT_DURATION } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index d39bd3d5eb12..720baec54190 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -83,19 +83,12 @@ constructor( private fun updateBlueprint() { val useSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources) - // TODO(b/326098079): Make ID a constant value. - val useWeatherClockLayout = - clockInteractor.currentClock.value?.config?.id == "DIGITAL_CLOCK_WEATHER" && - ComposeLockscreen.isEnabled val blueprintId = when { - useWeatherClockLayout && useSplitShade -> SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID - useWeatherClockLayout -> WEATHER_CLOCK_BLUEPRINT_ID useSplitShade && !ComposeLockscreen.isEnabled -> SplitShadeKeyguardBlueprint.ID else -> DefaultKeyguardBlueprint.DEFAULT } - transitionToBlueprint(blueprintId) } @@ -128,13 +121,4 @@ constructor( fun getCurrentBlueprint(): KeyguardBlueprint { return keyguardBlueprintRepository.blueprint.value } - - companion object { - /** - * These values live here because classes in the composable package do not exist in some - * systems. - */ - const val WEATHER_CLOCK_BLUEPRINT_ID = "weather-clock" - const val SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID = "split-shade-weather-clock" - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt index d551c9b9a4de..f7f60a5a72a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -21,23 +21,48 @@ import android.util.Log import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch import com.android.keyguard.KeyguardClockSwitch.ClockSize +import com.android.keyguard.KeyguardClockSwitch.LARGE +import com.android.keyguard.KeyguardClockSwitch.SMALL import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardClockRepository +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockId +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor +import com.android.systemui.util.kotlin.combine import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn private val TAG = KeyguardClockInteractor::class.simpleName -/** Manages and ecapsulates the clock components of the lockscreen root view. */ +/** Manages and encapsulates the clock components of the lockscreen root view. */ @SysUISingleton class KeyguardClockInteractor @Inject constructor( + mediaCarouselInteractor: MediaCarouselInteractor, + activeNotificationsInteractor: ActiveNotificationsInteractor, + shadeInteractor: ShadeInteractor, + keyguardInteractor: KeyguardInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + headsUpNotificationInteractor: HeadsUpNotificationInteractor, + @Application private val applicationScope: CoroutineScope, private val keyguardClockRepository: KeyguardClockRepository, ) { + private val isOnAod: Flow<Boolean> = + keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.AOD } val selectedClockSize: StateFlow<SettingsClockSize> = keyguardClockRepository.selectedClockSize @@ -51,7 +76,64 @@ constructor( var clock: ClockController? by keyguardClockRepository.clockEventController::clock - val clockSize: StateFlow<Int> = keyguardClockRepository.clockSize + // TODO (b/333389512): Convert this into a more readable enum. + val clockSize: StateFlow<Int> = + if (SceneContainerFlag.isEnabled) { + combine( + shadeInteractor.shadeMode, + activeNotificationsInteractor.areAnyNotificationsPresent, + mediaCarouselInteractor.hasActiveMediaOrRecommendation, + keyguardInteractor.isDozing, + isOnAod, + ) { shadeMode, hasNotifs, hasMedia, isDozing, isOnAod -> + return@combine when { + keyguardClockRepository.shouldForceSmallClock && !isOnAod -> SMALL + shadeMode == ShadeMode.Single && (hasNotifs || hasMedia) -> SMALL + shadeMode == ShadeMode.Single -> LARGE + hasMedia && !isDozing -> SMALL + else -> LARGE + } + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = LARGE + ) + } else { + SceneContainerFlag.assertInLegacyMode() + keyguardClockRepository.clockSize + } + + val clockShouldBeCentered: Flow<Boolean> = + if (SceneContainerFlag.isEnabled) { + combine( + shadeInteractor.shadeMode, + activeNotificationsInteractor.areAnyNotificationsPresent, + keyguardInteractor.isActiveDreamLockscreenHosted, + isOnAod, + headsUpNotificationInteractor.isHeadsUpOrAnimatingAway, + keyguardInteractor.isDozing, + ) { + shadeMode, + areAnyNotificationsPresent, + isActiveDreamLockscreenHosted, + isOnAod, + isHeadsUp, + isDozing -> + when { + shadeMode != ShadeMode.Split -> true + !areAnyNotificationsPresent -> true + isActiveDreamLockscreenHosted -> true + // Pulsing notification appears on the right. Move clock left to avoid overlap. + isHeadsUp && isDozing -> false + else -> isOnAod + } + } + } else { + SceneContainerFlag.assertInLegacyMode() + keyguardInteractor.clockShouldBeCentered + } + fun setClockSize(@ClockSize size: Int) { keyguardClockRepository.setClockSize(size) } 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 2182fe378d2f..c4769488646d 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 @@ -210,7 +210,8 @@ constructor( keyguardTransitionInteractor .transitionValue(GONE) .map { it == 1f } - .onStart { emit(false) }, + .onStart { emit(false) } + .distinctUntilChanged(), repository.topClippingBounds ) { _, isGone, topClippingBounds -> if (!isGone) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt new file mode 100644 index 000000000000..a43eb710e880 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/RefactorKeyguardDismissIntent.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the refactor_keyguard_dismiss_intent flag. */ +@Suppress("NOTHING_TO_INLINE") +object RefactorKeyguardDismissIntent { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.refactorKeyguardDismissIntent() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index e423fe0bd8a0..f46a207b273a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -25,7 +25,6 @@ import android.view.View import androidx.core.view.isInvisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.ui.view.DeviceEntryIconView @@ -35,15 +34,13 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch @ExperimentalCoroutinesApi object DeviceEntryIconViewBinder { - private const val TAG = "DeviceEntryIconViewBinder" - /** * Updates UI for: * - device entry containing view (parent view for the below views) @@ -61,7 +58,6 @@ object DeviceEntryIconViewBinder { bgViewModel: DeviceEntryBackgroundViewModel, falsingManager: FalsingManager, vibratorHelper: VibratorHelper, - mainImmediateDispatcher: CoroutineDispatcher, ) { DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() val longPressHandlingView = view.longPressHandlingView @@ -77,33 +73,31 @@ object DeviceEntryIconViewBinder { view, HapticFeedbackConstants.CONFIRM, ) - applicationScope.launch("$TAG#viewModel.onLongPress") { - viewModel.onLongPress() - } + applicationScope.launch { viewModel.onLongPress() } } } - view.repeatWhenAttached(mainImmediateDispatcher) { + view.repeatWhenAttached { // Repeat on CREATED so that the view will always observe the entire // GONE => AOD transition (even though the view may not be visible until the middle // of the transition. repeatOnLifecycle(Lifecycle.State.CREATED) { - launch("$TAG#viewModel.isVisible") { + launch { viewModel.isVisible.collect { isVisible -> longPressHandlingView.isInvisible = !isVisible } } - launch("$TAG#viewModel.isLongPressEnabled") { + launch { viewModel.isLongPressEnabled.collect { isEnabled -> longPressHandlingView.setLongPressHandlingEnabled(isEnabled) } } - launch("$TAG#viewModel.accessibilityDelegateHint") { + launch { viewModel.accessibilityDelegateHint.collect { hint -> view.accessibilityHintType = hint } } - launch("$TAG#viewModel.useBackgroundProtection") { + launch { viewModel.useBackgroundProtection.collect { useBackgroundProtection -> if (useBackgroundProtection) { bgView.visibility = View.VISIBLE @@ -112,7 +106,7 @@ object DeviceEntryIconViewBinder { } } } - launch("$TAG#viewModel.burnInOffsets") { + launch { viewModel.burnInOffsets.collect { burnInOffsets -> view.translationX = burnInOffsets.x.toFloat() view.translationY = burnInOffsets.y.toFloat() @@ -120,17 +114,15 @@ object DeviceEntryIconViewBinder { } } - launch("$TAG#viewModel.deviceEntryViewAlpha") { - viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } - } + launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } } } } - fgIconView.repeatWhenAttached(mainImmediateDispatcher) { + fgIconView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { // Start with an empty state fgIconView.setImageState(StateSet.NOTHING, /* merge */ false) - launch("$TAG#fgViewModel.viewModel") { + launch { fgViewModel.viewModel.collect { viewModel -> fgIconView.setImageState( view.getIconState(viewModel.type, viewModel.useAodVariant), @@ -150,10 +142,8 @@ object DeviceEntryIconViewBinder { bgView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - launch("$TAG#bgViewModel.alpha") { - bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } - } - launch("$TAG#bgViewModel.color") { + launch { bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha } } + launch { bgViewModel.color.collect { color -> bgView.imageTintList = ColorStateList.valueOf(color) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt index 1b06a69213b9..6255f0d44609 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt @@ -26,7 +26,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.keyguard.KeyguardClockSwitch.SMALL import com.android.systemui.keyguard.MigrateClocksToBlueprint @@ -38,10 +37,10 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID -import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.launch object KeyguardClockViewBinder { - private const val TAG = "KeyguardClockViewBinder" + private val TAG = KeyguardClockViewBinder::class.simpleName!! // When changing to new clock, we need to remove old clock views from burnInLayer private var lastClock: ClockController? = null @JvmStatic @@ -51,12 +50,15 @@ object KeyguardClockViewBinder { viewModel: KeyguardClockViewModel, keyguardClockInteractor: KeyguardClockInteractor, blueprintInteractor: KeyguardBlueprintInteractor, - ): DisposableHandle { - keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView) - - return keyguardRootView.repeatWhenAttached { + ) { + keyguardRootView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + keyguardClockInteractor.clockEventController.registerListeners(keyguardRootView) + } + } + keyguardRootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - launch("$TAG#viewModel.currentClock") { + launch { if (!MigrateClocksToBlueprint.isEnabled) return@launch viewModel.currentClock.collect { currentClock -> cleanupClockViews(currentClock, keyguardRootView, viewModel.burnInLayer) @@ -65,14 +67,14 @@ object KeyguardClockViewBinder { applyConstraints(clockSection, keyguardRootView, true) } } - launch("$TAG#viewModel.clockSize") { + launch { if (!MigrateClocksToBlueprint.isEnabled) return@launch viewModel.clockSize.collect { updateBurnInLayer(keyguardRootView, viewModel) blueprintInteractor.refreshBlueprint(Type.ClockSize) } } - launch("$TAG#viewModel.clockShouldBeCentered") { + launch { if (!MigrateClocksToBlueprint.isEnabled) return@launch viewModel.clockShouldBeCentered.collect { clockShouldBeCentered -> viewModel.currentClock.value?.let { @@ -89,7 +91,7 @@ object KeyguardClockViewBinder { } } } - launch("$TAG#viewModel.isAodIconsVisible") { + launch { if (!MigrateClocksToBlueprint.isEnabled) return@launch viewModel.isAodIconsVisible.collect { isAodIconsVisible -> viewModel.currentClock.value?.let { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt index d5add61822b1..93b3ba561eb3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt @@ -19,9 +19,8 @@ import com.android.keyguard.logging.KeyguardLogger import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent import com.android.systemui.log.core.LogLevel import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -38,11 +37,10 @@ constructor( private val interactor: KeyguardDismissActionInteractor, @Application private val scope: CoroutineScope, private val keyguardLogger: KeyguardLogger, - private val featureFlags: FeatureFlagsClassic, ) : CoreStartable { override fun start() { - if (!featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (!RefactorKeyguardDismissIntent.isEnabled) { return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt index 87d8164e1cb9..f77d01208dc2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt @@ -21,8 +21,8 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor +import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.log.core.LogLevel import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -44,7 +44,7 @@ constructor( ) : CoreStartable { override fun start() { - if (!featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (!RefactorKeyguardDismissIntent.isEnabled) { return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index 486320af45a2..3ff32bfcfd46 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -44,6 +44,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.res.R +import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.util.Utils import kotlin.reflect.KSuspendFunction1 @@ -77,37 +78,42 @@ object KeyguardPreviewClockViewBinder { context: Context, rootView: ConstraintLayout, viewModel: KeyguardPreviewClockViewModel, + clockRegistry: ClockRegistry, updateClockAppearance: KSuspendFunction1<ClockController, Unit>, ) { rootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { + var lastClock: ClockController? = null launch("$TAG#viewModel.previewClock") { - var lastClock: ClockController? = null - viewModel.previewClock.collect { currentClock -> - lastClock?.let { clock -> - (clock.largeClock.layout.views + clock.smallClock.layout.views) - .forEach { rootView.removeView(it) } - } - lastClock = currentClock - updateClockAppearance(currentClock) + viewModel.previewClock.collect { currentClock -> + lastClock?.let { clock -> + (clock.largeClock.layout.views + clock.smallClock.layout.views) + .forEach { rootView.removeView(it) } + } + lastClock = currentClock + updateClockAppearance(currentClock) - if (viewModel.shouldHighlightSelectedAffordance) { - (currentClock.largeClock.layout.views + - currentClock.smallClock.layout.views) - .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA } - } - currentClock.largeClock.layout.views.forEach { - (it.parent as? ViewGroup)?.removeView(it) - rootView.addView(it) - } + if (viewModel.shouldHighlightSelectedAffordance) { + (currentClock.largeClock.layout.views + + currentClock.smallClock.layout.views) + .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA } + } + currentClock.largeClock.layout.views.forEach { + (it.parent as? ViewGroup)?.removeView(it) + rootView.addView(it) + } - currentClock.smallClock.layout.views.forEach { - (it.parent as? ViewGroup)?.removeView(it) - rootView.addView(it) + currentClock.smallClock.layout.views.forEach { + (it.parent as? ViewGroup)?.removeView(it) + rootView.addView(it) + } + applyPreviewConstraints(context, rootView, currentClock, viewModel) } - applyPreviewConstraints(context, rootView, currentClock, viewModel) } - } + .invokeOnCompletion { + // recover seed color especially for Transit clock + lastClock?.events?.onSeedColorChanged(clockRegistry.seedColor) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt index 49ae35a43c23..88d907469f69 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt @@ -32,7 +32,7 @@ object KeyguardPreviewSmartspaceViewBinder { @JvmStatic fun bind( - context: Context, + previewContext: Context, smartspace: View, splitShadePreview: Boolean, viewModel: KeyguardPreviewSmartspaceViewModel, @@ -46,10 +46,12 @@ object KeyguardPreviewSmartspaceViewBinder { SettingsClockSize.DYNAMIC -> viewModel.getLargeClockSmartspaceTopPadding( splitShadePreview, + previewContext, ) SettingsClockSize.SMALL -> viewModel.getSmallClockSmartspaceTopPadding( splitShadePreview, + previewContext, ) } smartspace.setTopPadding(topPadding) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 6c21e6cdb3bc..abd79ab793d5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -30,7 +30,6 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch import com.android.settingslib.Utils import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableImageView @@ -42,11 +41,11 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.doOnEnd -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch /** This is only for a SINGLE Quick affordance */ object KeyguardQuickAffordanceViewBinder { @@ -54,7 +53,6 @@ object KeyguardQuickAffordanceViewBinder { private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L private const val SCALE_SELECTED_BUTTON = 1.23f private const val DIM_ALPHA = 0.3f - private const val TAG = "KeyguardQuickAffordanceViewBinder" /** * Defines interface for an object that acts as the binding between the view and its view-model. @@ -76,15 +74,14 @@ object KeyguardQuickAffordanceViewBinder { alpha: Flow<Float>, falsingManager: FalsingManager?, vibratorHelper: VibratorHelper?, - mainImmediateDispatcher: CoroutineDispatcher, messageDisplayer: (Int) -> Unit, ): Binding { val button = view as ImageView val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val disposableHandle = - view.repeatWhenAttached(mainImmediateDispatcher) { + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { - launch("$TAG#viewModel.collect") { + launch { viewModel.collect { buttonModel -> updateButton( view = button, @@ -96,7 +93,7 @@ object KeyguardQuickAffordanceViewBinder { } } - launch("$TAG#updateButtonAlpha") { + launch { updateButtonAlpha( view = button, viewModel = viewModel, @@ -104,7 +101,7 @@ object KeyguardQuickAffordanceViewBinder { ) } - launch("$TAG#configurationBasedDimensions") { + launch { configurationBasedDimensions.collect { dimensions -> button.updateLayoutParams<ViewGroup.LayoutParams> { width = dimensions.buttonSizePx.width diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 44fd58267250..5ee35e4f8eb6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -33,7 +33,6 @@ import android.view.WindowInsets import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.systemui.Flags.newAodTransition @@ -73,7 +72,6 @@ import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.stopAnimating import com.android.systemui.util.ui.value import kotlin.math.min -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope @@ -81,6 +79,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */ @OptIn(ExperimentalCoroutinesApi::class) @@ -101,7 +100,6 @@ object KeyguardRootViewBinder { vibratorHelper: VibratorHelper?, falsingManager: FalsingManager?, keyguardViewMediator: KeyguardViewMediator?, - mainImmediateDispatcher: CoroutineDispatcher, ): DisposableHandle { val disposables = DisposableHandles() val childViews = mutableMapOf<Int, View>() @@ -125,9 +123,9 @@ object KeyguardRootViewBinder { ) disposables += - view.repeatWhenAttached(mainImmediateDispatcher) { + view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - launch("$TAG#occludingAppDeviceEntryMessageViewModel.message") { + launch { occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage -> if (biometricMessage?.message != null) { @@ -146,7 +144,7 @@ object KeyguardRootViewBinder { if ( KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled ) { - launch("$TAG#viewModel.alpha") { + launch { viewModel.alpha(viewState).collect { alpha -> view.alpha = alpha if (KeyguardBottomAreaRefactor.isEnabled) { @@ -158,21 +156,21 @@ object KeyguardRootViewBinder { } if (MigrateClocksToBlueprint.isEnabled) { - launch("$TAG#viewModel.burnInLayerVisibility") { + launch { viewModel.burnInLayerVisibility.collect { visibility -> childViews[burnInLayerId]?.visibility = visibility childViews[aodNotificationIconContainerId]?.visibility = visibility } } - launch("$TAG#viewModel.burnInLayerAlpha") { + launch { viewModel.burnInLayerAlpha.collect { alpha -> childViews[statusViewId]?.alpha = alpha childViews[aodNotificationIconContainerId]?.alpha = alpha } } - launch("$TAG#viewModel.topClippingBounds") { + launch { val clipBounds = Rect() viewModel.topClippingBounds.collect { clipTop -> if (clipTop == null) { @@ -189,13 +187,13 @@ object KeyguardRootViewBinder { } } - launch("$TAG#viewModel.lockscreenStateAlpha") { + launch { viewModel.lockscreenStateAlpha(viewState).collect { alpha -> childViews[statusViewId]?.alpha = alpha } } - launch("$TAG#viewModel.translationY") { + launch { // When translation happens in burnInLayer, it won't be weather clock // large clock isn't added to burnInLayer due to its scale transition // so we also need to add translation to it here @@ -207,7 +205,7 @@ object KeyguardRootViewBinder { } } - launch("$TAG#viewModel.translationX") { + launch { viewModel.translationX.collect { state -> val px = state.value ?: return@collect when { @@ -234,7 +232,7 @@ object KeyguardRootViewBinder { } } - launch("$TAG#viewModel.scale") { + launch { viewModel.scale.collect { scaleViewModel -> if (scaleViewModel.scaleClockOnly) { // For clocks except weather clock, we have scale transition @@ -265,7 +263,7 @@ object KeyguardRootViewBinder { } if (NotificationIconContainerRefactor.isEnabled) { - launch("$TAG#viewModel.isNotifIconContainerVisible") { + launch { val iconsAppearTranslationPx = configuration .getDimensionPixelSize(R.dimen.shelf_appear_translation) @@ -282,7 +280,7 @@ object KeyguardRootViewBinder { } interactionJankMonitor?.let { jankMonitor -> - launch("$TAG#viewModel.goneToAodTransition") { + launch { viewModel.goneToAodTransition.collect { when (it.transitionState) { TransitionState.STARTED -> { @@ -308,7 +306,7 @@ object KeyguardRootViewBinder { } } - launch("$TAG#shadeInteractor.isAnyFullyExpanded") { + launch { shadeInteractor.isAnyFullyExpanded.collect { isFullyAnyExpanded -> view.visibility = if (isFullyAnyExpanded) { @@ -319,12 +317,10 @@ object KeyguardRootViewBinder { } } - launch("$TAG#burnInParams.collect") { - burnInParams.collect { viewModel.updateBurnInParams(it) } - } + launch { burnInParams.collect { viewModel.updateBurnInParams(it) } } if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { - launch("$TAG#deviceEntryHapticsInteractor.playSuccessHaptic") { + launch { deviceEntryHapticsInteractor.playSuccessHaptic.collect { vibratorHelper.performHapticFeedback( view, @@ -334,7 +330,7 @@ object KeyguardRootViewBinder { } } - launch("$TAG#deviceEntryHapticsInteractor.playErrorHaptic") { + launch { deviceEntryHapticsInteractor.playErrorHaptic.collect { vibratorHelper.performHapticFeedback( view, @@ -589,5 +585,4 @@ object KeyguardRootViewBinder { private const val ID = "occluding_app_device_entry_unlock_msg" private const val AOD_ICONS_APPEAR_DURATION: Long = 200 - private const val TAG = "KeyguardRootViewBinder" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index ce1aed08ab49..bda5be4f6533 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -327,9 +327,12 @@ constructor( smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView) val topPadding: Int = - smartspaceViewModel.getLargeClockSmartspaceTopPadding(previewInSplitShade()) - val startPadding: Int = smartspaceViewModel.getSmartspaceStartPadding() - val endPadding: Int = smartspaceViewModel.getSmartspaceEndPadding() + smartspaceViewModel.getLargeClockSmartspaceTopPadding( + previewInSplitShade(), + previewContext, + ) + val startPadding: Int = smartspaceViewModel.getSmartspaceStartPadding(previewContext) + val endPadding: Int = smartspaceViewModel.getSmartspaceEndPadding(previewContext) smartSpaceView?.let { it.setPaddingRelative(startPadding, topPadding, endPadding, 0) @@ -387,7 +390,6 @@ constructor( null, // device entry haptics not required for preview mode null, // falsing manager not required for preview mode null, // keyguard view mediator is not required for preview mode - mainDispatcher, ) } rootView.addView( @@ -411,10 +413,11 @@ constructor( setUpClock(previewContext, rootView) if (MigrateClocksToBlueprint.isEnabled) { KeyguardPreviewClockViewBinder.bind( - context, + previewContext, keyguardRootView, clockViewModel, - ::updateClockAppearance + clockRegistry, + ::updateClockAppearance, ) } else { KeyguardPreviewClockViewBinder.bind( @@ -429,7 +432,7 @@ constructor( smartSpaceView?.let { KeyguardPreviewSmartspaceViewBinder.bind( - context, + previewContext, it, previewInSplitShade(), smartspaceViewModel @@ -454,7 +457,6 @@ constructor( alpha = flowOf(1f), falsingManager = falsingManager, vibratorHelper = vibratorHelper, - mainImmediateDispatcher = mainDispatcher, ) { message -> indicationController.showTransientIndication(message) } @@ -469,7 +471,6 @@ constructor( alpha = flowOf(1f), falsingManager = falsingManager, vibratorHelper = vibratorHelper, - mainImmediateDispatcher = mainDispatcher, ) { message -> indicationController.showTransientIndication(message) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt index b4e57cc93962..04ac7bf1178e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt @@ -17,13 +17,9 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID import com.android.systemui.keyguard.shared.model.KeyguardBlueprint -import com.android.systemui.keyguard.shared.model.KeyguardSection import dagger.Binds import dagger.Module -import dagger.Provides import dagger.multibindings.IntoSet @Module @@ -45,26 +41,4 @@ abstract class KeyguardBlueprintModule { abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint( shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint ): KeyguardBlueprint - - companion object { - /** This is a place holder for weather clock in compose. */ - @Provides - @IntoSet - fun bindWeatherClockBlueprintPlaceHolder(): KeyguardBlueprint { - return object : KeyguardBlueprint { - override val id: String = WEATHER_CLOCK_BLUEPRINT_ID - override val sections: List<KeyguardSection> = listOf() - } - } - - /** This is a place holder for weather clock in compose. */ - @Provides - @IntoSet - fun bindSplitShadeWeatherClockBlueprintPlaceHolder(): KeyguardBlueprint { - return object : KeyguardBlueprint { - override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID - override val sections: List<KeyguardSection> = listOf() - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt index 5404729d1819..2e9663897f89 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt @@ -36,7 +36,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher class AlignShortcutsToUdfpsSection @Inject @@ -48,7 +47,6 @@ constructor( private val falsingManager: FalsingManager, private val indicationController: KeyguardIndicationController, private val vibratorHelper: VibratorHelper, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, ) : BaseShortcutSection() { override fun addViews(constraintLayout: ConstraintLayout) { if (KeyguardBottomAreaRefactor.isEnabled) { @@ -66,7 +64,6 @@ constructor( keyguardQuickAffordancesCombinedViewModel.transitionAlpha, falsingManager, vibratorHelper, - mainImmediateDispatcher, ) { indicationController.showTransientIndication(it) } @@ -77,7 +74,6 @@ constructor( keyguardQuickAffordancesCombinedViewModel.transitionAlpha, falsingManager, vibratorHelper, - mainImmediateDispatcher, ) { indicationController.showTransientIndication(it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index e0bf8152d11f..78a1fcfe4258 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -45,7 +45,6 @@ import com.android.systemui.res.R import com.android.systemui.shared.R as sharedR import dagger.Lazy import javax.inject.Inject -import kotlinx.coroutines.DisposableHandle internal fun ConstraintSet.setVisibility( views: Iterable<View>, @@ -66,23 +65,19 @@ constructor( val smartspaceViewModel: KeyguardSmartspaceViewModel, val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>, ) : KeyguardSection() { - private var handle: DisposableHandle? = null - override fun addViews(constraintLayout: ConstraintLayout) {} override fun bindData(constraintLayout: ConstraintLayout) { if (!MigrateClocksToBlueprint.isEnabled) { return } - handle?.dispose() - handle = - KeyguardClockViewBinder.bind( - this, - constraintLayout, - keyguardClockViewModel, - clockInteractor, - blueprintInteractor.get() - ) + KeyguardClockViewBinder.bind( + this, + constraintLayout, + keyguardClockViewModel, + clockInteractor, + blueprintInteractor.get() + ) } override fun applyConstraints(constraintSet: ConstraintSet) { @@ -94,13 +89,7 @@ constructor( } } - override fun removeViews(constraintLayout: ConstraintLayout) { - if (!MigrateClocksToBlueprint.isEnabled) { - return - } - handle?.dispose() - handle = null - } + override fun removeViews(constraintLayout: ConstraintLayout) {} private fun buildConstraints( clock: ClockController, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 865e989c5b68..29041d1665c3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -30,7 +30,6 @@ import com.android.keyguard.LockIconView import com.android.keyguard.LockIconViewController import com.android.systemui.biometrics.AuthController import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -48,7 +47,6 @@ import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.VibratorHelper import dagger.Lazy import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -69,7 +67,6 @@ constructor( private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, private val falsingManager: Lazy<FalsingManager>, private val vibratorHelper: Lazy<VibratorHelper>, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, ) : KeyguardSection() { private val deviceEntryIconViewId = R.id.device_entry_icon_view @@ -107,7 +104,6 @@ constructor( deviceEntryBackgroundViewModel.get(), falsingManager.get(), vibratorHelper.get(), - mainImmediateDispatcher, ) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index 27ca5cdbad17..45b82576c6c4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt @@ -35,7 +35,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher class DefaultShortcutsSection @Inject @@ -47,7 +46,6 @@ constructor( private val falsingManager: FalsingManager, private val indicationController: KeyguardIndicationController, private val vibratorHelper: VibratorHelper, - @Main private val mainImmediateDispatcher: CoroutineDispatcher, ) : BaseShortcutSection() { override fun addViews(constraintLayout: ConstraintLayout) { if (KeyguardBottomAreaRefactor.isEnabled) { @@ -65,7 +63,6 @@ constructor( keyguardQuickAffordancesCombinedViewModel.transitionAlpha, falsingManager, vibratorHelper, - mainImmediateDispatcher, ) { indicationController.showTransientIndication(it) } @@ -76,7 +73,6 @@ constructor( keyguardQuickAffordancesCombinedViewModel.transitionAlpha, falsingManager, vibratorHelper, - mainImmediateDispatcher, ) { indicationController.showTransientIndication(it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt index fe88b8169c89..6d0f96c2f635 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt @@ -18,9 +18,8 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.ScrimAlpha @@ -44,13 +43,12 @@ constructor( private val statusBarStateController: SysuiStatusBarStateController, private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>, - private val featureFlags: FeatureFlagsClassic, private val shadeInteractor: ShadeInteractor, private val animationFlow: KeyguardTransitionAnimationFlow, ) { /** Common fade for scrim alpha values during *BOUNCER->GONE */ fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> { - return if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + return if (RefactorKeyguardDismissIntent.isEnabled) { keyguardDismissActionInteractor .get() .willAnimateDismissActionOnLockscreen diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt index c9251c7c5473..f6da033f1bd3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt @@ -26,7 +26,6 @@ import com.android.systemui.customization.R as customizationR import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.SettingsClockSize import com.android.systemui.res.R @@ -46,11 +45,10 @@ import kotlinx.coroutines.flow.stateIn class KeyguardClockViewModel @Inject constructor( - keyguardInteractor: KeyguardInteractor, - private val keyguardClockInteractor: KeyguardClockInteractor, + keyguardClockInteractor: KeyguardClockInteractor, @Application private val applicationScope: CoroutineScope, notifsKeyguardInteractor: NotificationsKeyguardInteractor, - @VisibleForTesting val shadeInteractor: ShadeInteractor, + @get:VisibleForTesting val shadeInteractor: ShadeInteractor, ) { var burnInLayer: Layer? = null val useLargeClock: Boolean @@ -99,7 +97,7 @@ constructor( ) val clockShouldBeCentered: StateFlow<Boolean> = - keyguardInteractor.clockShouldBeCentered.stateIn( + keyguardClockInteractor.clockShouldBeCentered.stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = false @@ -113,18 +111,38 @@ constructor( ) val currentClockLayout: StateFlow<ClockLayout> = - combine(isLargeClockVisible, clockShouldBeCentered, shadeInteractor.shadeMode) { + combine( isLargeClockVisible, clockShouldBeCentered, - shadeMode -> + shadeInteractor.shadeMode, + currentClock + ) { isLargeClockVisible, clockShouldBeCentered, shadeMode, currentClock -> val shouldUseSplitShade = shadeMode == ShadeMode.Split - when { - shouldUseSplitShade && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK - shouldUseSplitShade && isLargeClockVisible -> - ClockLayout.SPLIT_SHADE_LARGE_CLOCK - shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK - isLargeClockVisible -> ClockLayout.LARGE_CLOCK - else -> ClockLayout.SMALL_CLOCK + // TODO(b/326098079): make id a constant field in config + if (currentClock?.config?.id == "DIGITAL_CLOCK_WEATHER") { + val weatherClockLayout = + when { + shouldUseSplitShade && clockShouldBeCentered -> + ClockLayout.WEATHER_LARGE_CLOCK + shouldUseSplitShade && isLargeClockVisible -> + ClockLayout.SPLIT_SHADE_WEATHER_LARGE_CLOCK + shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK + isLargeClockVisible -> ClockLayout.WEATHER_LARGE_CLOCK + else -> ClockLayout.SMALL_CLOCK + } + weatherClockLayout + } else { + val clockLayout = + when { + shouldUseSplitShade && clockShouldBeCentered -> ClockLayout.LARGE_CLOCK + shouldUseSplitShade && isLargeClockVisible -> + ClockLayout.SPLIT_SHADE_LARGE_CLOCK + shouldUseSplitShade -> ClockLayout.SPLIT_SHADE_SMALL_CLOCK + isLargeClockVisible -> ClockLayout.LARGE_CLOCK + else -> ClockLayout.SMALL_CLOCK + } + + clockLayout } } .stateIn( @@ -179,5 +197,7 @@ constructor( SMALL_CLOCK, SPLIT_SHADE_LARGE_CLOCK, SPLIT_SHADE_SMALL_CLOCK, + WEATHER_LARGE_CLOCK, + SPLIT_SHADE_WEATHER_LARGE_CLOCK, } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt index 4f2c6f576904..730015202a21 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt @@ -16,13 +16,10 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.content.Context -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.SettingsClockSize import com.android.systemui.plugins.clocks.ClockController import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -31,9 +28,7 @@ import kotlinx.coroutines.flow.map class KeyguardPreviewClockViewModel @Inject constructor( - @Application private val context: Context, interactor: KeyguardClockInteractor, - @Application private val applicationScope: CoroutineScope, ) { var shouldHighlightSelectedAffordance: Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt index b57e3ecbe05b..528b14c6bbd7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.SettingsClockSize import com.android.systemui.res.R @@ -31,7 +30,6 @@ import kotlinx.coroutines.flow.map class KeyguardPreviewSmartspaceViewModel @Inject constructor( - @Application private val context: Context, interactor: KeyguardClockInteractor, val smartspaceViewModel: KeyguardSmartspaceViewModel, val clockViewModel: KeyguardClockViewModel, @@ -55,29 +53,29 @@ constructor( } } - fun getSmartspaceStartPadding(): Int { + fun getSmartspaceStartPadding(context: Context): Int { return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context) } - fun getSmartspaceEndPadding(): Int { + fun getSmartspaceEndPadding(context: Context): Int { return KeyguardSmartspaceViewModel.getSmartspaceEndMargin(context) } - fun getSmallClockSmartspaceTopPadding(splitShadePreview: Boolean): Int { - return getSmallClockTopPadding(splitShadePreview) + + fun getSmallClockSmartspaceTopPadding(splitShadePreview: Boolean, context: Context): Int { + return getSmallClockTopPadding(splitShadePreview, context) + context.resources.getDimensionPixelSize( com.android.systemui.customization.R.dimen.small_clock_height ) } - fun getLargeClockSmartspaceTopPadding(splitShadePreview: Boolean): Int { - return getSmallClockTopPadding(splitShadePreview) + fun getLargeClockSmartspaceTopPadding(splitShadePreview: Boolean, context: Context): Int { + return getSmallClockTopPadding(splitShadePreview, context) } /* * SmallClockTopPadding decides the top position of smartspace */ - private fun getSmallClockTopPadding(splitShadePreview: Boolean): Int { + private fun getSmallClockTopPadding(splitShadePreview: Boolean, context: Context): Int { return with(context.resources) { if (splitShadePreview) { getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) 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 64e15659d08b..ac67f94fa9cf 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 @@ -185,8 +185,12 @@ constructor( .transitionValue(OCCLUDED) .map { it == 1f } .onStart { emit(false) }, - ) { isIdleOnCommunal, isGone, isOccluded -> - isIdleOnCommunal || isGone || isOccluded + keyguardTransitionInteractor + .transitionValue(KeyguardState.DREAMING) + .map { it == 1f } + .onStart { emit(false) }, + ) { isIdleOnCommunal, isGone, isOccluded, isDreaming -> + isIdleOnCommunal || isGone || isOccluded || isDreaming } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 993e81bfbf69..d4c8456e0d71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -37,6 +37,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user input for the lockscreen scene. */ @@ -52,16 +54,23 @@ constructor( val notifications: NotificationsPlaceholderViewModel, ) { val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = - combine( - deviceEntryInteractor.isUnlocked, - communalInteractor.isCommunalAvailable, - shadeInteractor.shadeMode, - ) { isDeviceUnlocked, isCommunalAvailable, shadeMode -> - destinationScenes( - isDeviceUnlocked = isDeviceUnlocked, - isCommunalAvailable = isCommunalAvailable, - shadeMode = shadeMode, - ) + shadeInteractor.isShadeTouchable + .flatMapLatest { isShadeTouchable -> + if (!isShadeTouchable) { + flowOf(emptyMap()) + } else { + combine( + deviceEntryInteractor.isUnlocked, + communalInteractor.isCommunalAvailable, + shadeInteractor.shadeMode, + ) { isDeviceUnlocked, isCommunalAvailable, shadeMode -> + destinationScenes( + isDeviceUnlocked = isDeviceUnlocked, + isCommunalAvailable = isCommunalAvailable, + shadeMode = shadeMode, + ) + } + } } .stateIn( scope = applicationScope, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt index 05878265dd6d..a08a234f85d4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -18,10 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.ScrimAlpha @@ -46,7 +45,6 @@ constructor( private val statusBarStateController: SysuiStatusBarStateController, private val primaryBouncerInteractor: PrimaryBouncerInteractor, keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>, - featureFlags: FeatureFlagsClassic, bouncerToGoneFlows: BouncerToGoneFlows, animationFlow: KeyguardTransitionAnimationFlow, ) { @@ -82,7 +80,7 @@ constructor( /** Bouncer container alpha */ val bouncerAlpha: Flow<Float> = - if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (RefactorKeyguardDismissIntent.isEnabled) { keyguardDismissActionInteractor .get() .willAnimateDismissActionOnLockscreen @@ -106,7 +104,7 @@ constructor( /** Lockscreen alpha */ val lockscreenAlpha: Flow<Float> = - if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (RefactorKeyguardDismissIntent.isEnabled) { keyguardDismissActionInteractor .get() .willAnimateDismissActionOnLockscreen diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 899b9ed103cd..ca898e675f53 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -118,8 +118,9 @@ import com.android.systemui.res.R; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.surfaceeffects.PaintDrawCallback; import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect; -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState; +import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.AnimationState; import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView; import com.android.systemui.surfaceeffects.ripple.MultiRippleController; import com.android.systemui.surfaceeffects.ripple.MultiRippleView; @@ -264,15 +265,15 @@ public class MediaControlPanel { private boolean mWasPlaying = false; private boolean mButtonClicked = false; - private final LoadingEffect.Companion.PaintDrawCallback mNoiseDrawCallback = - new LoadingEffect.Companion.PaintDrawCallback() { + private final PaintDrawCallback mNoiseDrawCallback = + new PaintDrawCallback() { @Override - public void onDraw(@NonNull Paint loadingPaint) { - mMediaViewHolder.getLoadingEffectView().draw(loadingPaint); + public void onDraw(@NonNull Paint paint) { + mMediaViewHolder.getLoadingEffectView().draw(paint); } }; - private final LoadingEffect.Companion.AnimationStateChangedCallback mStateChangedCallback = - new LoadingEffect.Companion.AnimationStateChangedCallback() { + private final LoadingEffect.AnimationStateChangedCallback mStateChangedCallback = + new LoadingEffect.AnimationStateChangedCallback() { @Override public void onStateChanged(@NonNull AnimationState oldState, @NonNull AnimationState newState) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 4e0b5762b6cf..ab0b0b7b7c6c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -52,8 +52,6 @@ constructor( val destinationScenes = qsSceneAdapter.isCustomizing.flatMapLatest { customizing -> if (customizing) { - // TODO(b/332749288) Empty map so there are no back handlers and back can close - // customizer flowOf(emptyMap()) // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade // while customizing diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 32d72e0bac22..1f935f97e771 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -47,6 +47,7 @@ import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.phone.CentralSurfaces @@ -103,6 +104,7 @@ constructor( private val headsUpInteractor: HeadsUpNotificationInteractor, private val occlusionInteractor: SceneContainerOcclusionInteractor, private val faceUnlockInteractor: DeviceEntryFaceAuthInteractor, + private val shadeInteractor: ShadeInteractor, ) : CoreStartable { override fun start() { @@ -185,6 +187,14 @@ constructor( /** Switches between scenes based on ever-changing application state. */ private fun automaticallySwitchScenes() { + handleBouncerImeVisibility() + handleSimUnlock() + handleDeviceUnlockStatus() + handlePowerState() + handleShadeTouchability() + } + + private fun handleBouncerImeVisibility() { applicationScope.launch { // TODO (b/308001302): Move this to a bouncer specific interactor. bouncerInteractor.onImeHiddenByUser.collectLatest { @@ -196,6 +206,9 @@ constructor( } } } + } + + private fun handleSimUnlock() { applicationScope.launch { simBouncerInteractor .get() @@ -229,6 +242,9 @@ constructor( } } } + } + + private fun handleDeviceUnlockStatus() { applicationScope.launch { deviceUnlockedInteractor.deviceUnlockStatus .mapNotNull { deviceUnlockStatus -> @@ -288,7 +304,9 @@ constructor( ) } } + } + private fun handlePowerState() { applicationScope.launch { powerInteractor.isAsleep.collect { isAsleep -> if (isAsleep) { @@ -317,7 +335,7 @@ constructor( ) { switchToScene( targetSceneKey = Scenes.Bouncer, - loggingReason = "device is starting to wake up with a locked sim" + loggingReason = "device is starting to wake up with a locked sim", ) } } @@ -325,6 +343,20 @@ constructor( } } + private fun handleShadeTouchability() { + applicationScope.launch { + shadeInteractor.isShadeTouchable + .distinctUntilChanged() + .filter { !it } + .collect { + switchToScene( + targetSceneKey = Scenes.Lockscreen, + loggingReason = "device became non-interactive", + ) + } + } + } + /** Keeps [SysUiState] up-to-date */ private fun hydrateSystemUiState() { applicationScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index c9291966bc15..cff11a753fe2 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -21,12 +21,14 @@ package com.android.systemui.scene.shared.flag import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.sceneContainer import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.ComposeLockscreen +import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent import com.android.systemui.media.controls.util.MediaInSceneContainerFlag import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag @@ -48,7 +50,9 @@ object SceneContainerFlag { MediaInSceneContainerFlag.isEnabled && MigrateClocksToBlueprint.isEnabled && NotificationsHeadsUpRefactor.isEnabled && - PredictiveBackSysUiFlag.isEnabled + PredictiveBackSysUiFlag.isEnabled && + DeviceEntryUdfpsRefactor.isEnabled && + RefactorKeyguardDismissIntent.isEnabled // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer /** The main aconfig flag. */ @@ -64,6 +68,8 @@ object SceneContainerFlag { MigrateClocksToBlueprint.token, NotificationsHeadsUpRefactor.token, PredictiveBackSysUiFlag.token, + DeviceEntryUdfpsRefactor.token, + RefactorKeyguardDismissIntent.token, // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer ) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java index ab8fc652c938..12bff499217e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java @@ -15,6 +15,7 @@ */ package com.android.systemui.screenshot; +import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.IAssistDataReceiver; @@ -55,7 +56,7 @@ public class AssistContentRequester { * Called when the {@link android.app.assist.AssistContent} of the requested task is * available. **/ - void onAssistContentAvailable(AssistContent assistContent); + void onAssistContentAvailable(@Nullable AssistContent assistContent); } private final IActivityTaskManager mActivityTaskManager; @@ -117,15 +118,9 @@ public class AssistContentRequester { @Override public void onHandleAssistData(Bundle data) { - if (data == null) { - return; - } - - final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT); - if (content == null) { - Log.e(TAG, "Received AssistData, but no AssistContent found"); - return; - } + final AssistContent content = (data == null) ? null + : data.getParcelable( + ASSIST_KEY_CONTENT, AssistContent.class); AssistContentRequester requester = mParentRef.get(); if (requester != null) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java index 6224e1bf2414..afb0280a3917 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java @@ -16,6 +16,7 @@ package com.android.systemui.screenshot; +import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -29,4 +30,9 @@ public interface ReferenceScreenshotModule { static ScreenshotNotificationSmartActionsProvider providesScrnshtNotifSmartActionsProvider() { return new ScreenshotNotificationSmartActionsProvider(); } + + /** */ + @Binds + ScreenshotActionsProvider.Factory bindScreenshotActionsProviderFactory( + DefaultScreenshotActionsProvider.Factory defaultScreenshotActionsProviderFactory); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index 0ccb19cd9d31..07e143a34319 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -28,7 +28,6 @@ import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubjec import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED -import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted @@ -43,7 +42,11 @@ interface ScreenshotActionsProvider { fun onScrollChipReady(onClick: Runnable) fun setCompletedScreenshot(result: ScreenshotSavedResult) - fun onAssistContentAvailable(assistContent: AssistContent) {} + /** + * Provide the AssistContent for the focused task if available, null if the focused task isn't + * known or didn't return data. + */ + fun onAssistContent(assistContent: AssistContent?) {} interface Factory { fun create( @@ -59,7 +62,6 @@ class DefaultScreenshotActionsProvider constructor( private val context: Context, private val viewModel: ScreenshotViewModel, - private val smartActionsProvider: SmartActionsProvider, private val uiEventLogger: UiEventLogger, @Assisted val request: ScreenshotData, @Assisted val requestId: String, @@ -115,37 +117,6 @@ constructor( ) } } - - smartActionsProvider.requestQuickShare(request, requestId) { quickShare -> - if (!quickShare.actionIntent.isImmutable) { - viewModel.addAction( - ActionButtonAppearance( - quickShare.getIcon().loadDrawable(context), - quickShare.title, - quickShare.title - ) - ) { - debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" } - onDeferrableActionTapped { result -> - uiEventLogger.log( - SCREENSHOT_SMART_ACTION_TAPPED, - 0, - request.packageNameString - ) - val pendingIntentWithUri = - smartActionsProvider.wrapIntent( - quickShare, - result.uri, - result.subject, - requestId - ) - actionExecutor.sendPendingIntent(pendingIntentWithUri) - } - } - } else { - Log.w(TAG, "Received immutable quick share pending intent; ignoring") - } - } } override fun onScrollChipReady(onClick: Runnable) { @@ -167,21 +138,6 @@ constructor( } this.result = result pendingAction?.invoke(result) - smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions -> - smartActions.forEach { - smartActions.forEach { action -> - viewModel.addAction( - ActionButtonAppearance( - action.getIcon().loadDrawable(context), - action.title, - action.title, - ) - ) { - actionExecutor.sendPendingIntent(action.actionIntent) - } - } - } - } } private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 13dd229d8f62..6871084aa938 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -176,11 +176,12 @@ public class ScreenshotController { // These strings are used for communicating the action invoked to // ScreenshotNotificationSmartActionsProvider. - static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; - static final String EXTRA_ID = "android:screenshot_id"; - static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; - static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; - static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin"; + public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type"; + public static final String EXTRA_ID = "android:screenshot_id"; + public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled"; + public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent"; + public static final String EXTRA_ACTION_INTENT_FILLIN = + "android:screenshot_action_intent_fillin"; // From WizardManagerHelper.java @@ -411,9 +412,9 @@ public class ScreenshotController { if (screenshot.getTaskId() >= 0) { mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), - assistContent -> { - mActionsProvider.onAssistContentAvailable(assistContent); - }); + assistContent -> mActionsProvider.onAssistContent(assistContent)); + } else { + mActionsProvider.onAssistContent(null); } } else { saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java index 3eafbfbf37d7..23f05e0d8337 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java @@ -44,7 +44,7 @@ public class ScreenshotNotificationSmartActionsProvider { public static final String DEFAULT_ACTION_TYPE = "Smart Action"; /* Define phases of screenshot execution. */ - protected enum ScreenshotOp { + public enum ScreenshotOp { OP_UNKNOWN, RETRIEVE_SMART_ACTIONS, REQUEST_SMART_ACTIONS, @@ -52,7 +52,7 @@ public class ScreenshotNotificationSmartActionsProvider { } /* Enum to report success or failure for screenshot execution phases. */ - protected enum ScreenshotOpStatus { + public enum ScreenshotOpStatus { OP_STATUS_UNKNOWN, SUCCESS, ERROR, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt deleted file mode 100644 index a895b300b900..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot - -import android.app.Notification -import android.app.PendingIntent -import android.content.ClipData -import android.content.ClipDescription -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.graphics.Bitmap -import android.net.Uri -import android.os.Bundle -import android.os.Process -import android.os.SystemClock -import android.os.UserHandle -import android.provider.DeviceConfig -import android.util.Log -import com.android.internal.config.sysui.SystemUiDeviceConfigFlags -import com.android.systemui.log.DebugLogger.debugLog -import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS -import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION -import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException -import javax.inject.Inject -import kotlin.random.Random - -/** - * Handle requesting smart/quickshare actions from the provider and executing an action when the - * action futures complete. - */ -class SmartActionsProvider -@Inject -constructor( - private val context: Context, - private val smartActions: ScreenshotNotificationSmartActionsProvider, -) { - /** - * Requests quick share action for a given screenshot. - * - * @param data the ScreenshotData request - * @param id the request id for the screenshot - * @param onAction callback to run when quick share action is returned - */ - fun requestQuickShare( - data: ScreenshotData, - id: String, - onAction: (Notification.Action) -> Unit - ) { - val bitmap = data.bitmap ?: return - val component = data.topComponent ?: ComponentName("", "") - requestQuickShareAction(id, bitmap, component, data.getUserOrDefault()) { quickShare -> - onAction(quickShare) - } - } - - /** - * Requests smart actions for a given screenshot. - * - * @param data the ScreenshotData request - * @param id the request id for the screenshot - * @param result the data for the saved image - * @param onActions callback to run when actions are returned - */ - fun requestSmartActions( - data: ScreenshotData, - id: String, - result: ScreenshotSavedResult, - onActions: (List<Notification.Action>) -> Unit - ) { - val bitmap = data.bitmap ?: return - val component = data.topComponent ?: ComponentName("", "") - requestSmartActions( - id, - bitmap, - component, - data.getUserOrDefault(), - result.uri, - REGULAR_SMART_ACTIONS - ) { actions -> - onActions(actions) - } - } - - /** - * Wraps the given quick share action in a broadcast intent. - * - * @param quickShare the quick share action to wrap - * @param uri the URI of the saved screenshot - * @param subject the subject/title for the screenshot - * @param id the request ID of the screenshot - * @return the pending intent with correct URI - */ - fun wrapIntent( - quickShare: Notification.Action, - uri: Uri, - subject: String, - id: String - ): PendingIntent { - val wrappedIntent: Intent = - Intent(context, SmartActionsReceiver::class.java) - .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent) - .putExtra( - ScreenshotController.EXTRA_ACTION_INTENT_FILLIN, - createFillInIntent(uri, subject) - ) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) - val extras: Bundle = quickShare.extras - val actionType = - extras.getString( - ScreenshotNotificationSmartActionsProvider.ACTION_TYPE, - ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE - ) - // We only query for quick share actions when smart actions are enabled, so we can assert - // that it's true here. - wrappedIntent - .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType) - .putExtra(ScreenshotController.EXTRA_ID, id) - .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true) - return PendingIntent.getBroadcast( - context, - Random.nextInt(), - wrappedIntent, - PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - } - - private fun createFillInIntent(uri: Uri, subject: String): Intent { - val fillIn = Intent() - fillIn.setType("image/png") - fillIn.putExtra(Intent.EXTRA_STREAM, uri) - fillIn.putExtra(Intent.EXTRA_SUBJECT, subject) - // Include URI in ClipData also, so that grantPermission picks it up. - // We don't use setData here because some apps interpret this as "to:". - val clipData = - ClipData(ClipDescription("content", arrayOf("image/png")), ClipData.Item(uri)) - fillIn.clipData = clipData - fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - return fillIn - } - - private fun requestQuickShareAction( - id: String, - image: Bitmap, - component: ComponentName, - user: UserHandle, - timeoutMs: Long = 500, - onAction: (Notification.Action) -> Unit - ) { - requestSmartActions(id, image, component, user, null, QUICK_SHARE_ACTION, timeoutMs) { - it.firstOrNull()?.let { action -> onAction(action) } - } - } - - private fun requestSmartActions( - id: String, - image: Bitmap, - component: ComponentName, - user: UserHandle, - uri: Uri?, - actionType: ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType, - timeoutMs: Long = 500, - onActions: (List<Notification.Action>) -> Unit - ) { - val enabled = isSmartActionsEnabled(user) - debugLog(DEBUG_ACTIONS) { - ("getSmartActionsFuture id=$id, uri=$uri, provider=$smartActions, " + - "actionType=$actionType, smartActionsEnabled=$enabled, userHandle=$user") - } - if (!enabled) { - debugLog(DEBUG_ACTIONS) { "Screenshot Intelligence not enabled, returning empty list" } - onActions(listOf()) - return - } - if (image.config != Bitmap.Config.HARDWARE) { - debugLog(DEBUG_ACTIONS) { - "Bitmap expected: Hardware, Bitmap found: ${image.config}. Returning empty list." - } - onActions(listOf()) - return - } - val smartActionsFuture: CompletableFuture<List<Notification.Action>> - val startTimeMs = SystemClock.uptimeMillis() - try { - smartActionsFuture = - smartActions.getActions(id, uri, image, component, actionType, user) - } catch (e: Throwable) { - val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs - debugLog(DEBUG_ACTIONS, error = e) { - "Failed to get future for screenshot notification smart actions." - } - notifyScreenshotOp( - id, - ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS, - ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR, - waitTimeMs - ) - onActions(listOf()) - return - } - try { - val actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS) - val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs - debugLog(DEBUG_ACTIONS) { - ("Got ${actions.size} smart actions. Wait time: $waitTimeMs ms, " + - "actionType=$actionType") - } - notifyScreenshotOp( - id, - ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS, - ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS, - waitTimeMs - ) - onActions(actions) - } catch (e: Throwable) { - val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs - debugLog(DEBUG_ACTIONS, error = e) { - "Error getting smart actions. Wait time: $waitTimeMs ms, actionType=$actionType" - } - val status = - if (e is TimeoutException) { - ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT - } else { - ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR - } - notifyScreenshotOp( - id, - ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS, - status, - waitTimeMs - ) - onActions(listOf()) - } - } - - private fun notifyScreenshotOp( - screenshotId: String, - op: ScreenshotNotificationSmartActionsProvider.ScreenshotOp, - status: ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus, - durationMs: Long - ) { - debugLog(DEBUG_ACTIONS) { - "$smartActions notifyOp: $op id=$screenshotId, status=$status, durationMs=$durationMs" - } - try { - smartActions.notifyOp(screenshotId, op, status, durationMs) - } catch (e: Throwable) { - Log.e(TAG, "Error in notifyScreenshotOp: ", e) - } - } - - private fun isSmartActionsEnabled(user: UserHandle): Boolean { - // Smart actions don't yet work for cross-user saves. - val savingToOtherUser = user !== Process.myUserHandle() - val actionsEnabled = - DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, - SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, - true - ) - return !savingToOtherUser && actionsEnabled - } - - companion object { - private const val TAG = "SmartActionsProvider" - private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java index 6ff0fdaeab0f..ab23e5fdb7d9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java @@ -22,11 +22,9 @@ import android.app.Service; import android.view.accessibility.AccessibilityManager; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.screenshot.DefaultScreenshotActionsProvider; import com.android.systemui.screenshot.ImageCapture; import com.android.systemui.screenshot.ImageCaptureImpl; import com.android.systemui.screenshot.LegacyScreenshotViewProxy; -import com.android.systemui.screenshot.ScreenshotActionsProvider; import com.android.systemui.screenshot.ScreenshotPolicy; import com.android.systemui.screenshot.ScreenshotPolicyImpl; import com.android.systemui.screenshot.ScreenshotShelfViewProxy; @@ -90,10 +88,6 @@ public abstract class ScreenshotModule { abstract ScreenshotSoundController bindScreenshotSoundController( ScreenshotSoundControllerImpl screenshotSoundProviderImpl); - @Binds - abstract ScreenshotActionsProvider.Factory bindScreenshotActionsProviderFactory( - DefaultScreenshotActionsProvider.Factory defaultScreenshotActionsProviderFactory); - @Provides @SysUISingleton static ScreenshotViewModel providesScreenshotViewModel( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt index 4a88180d8f73..0fb536636f1c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt @@ -22,7 +22,28 @@ import com.android.systemui.screenshot.data.model.DisplayContentModel fun interface CapturePolicy { /** * Test the policy against the current display task state. If the policy applies, Returns a - * non-null [CaptureParameters] describing how the screenshot request should be augmented. + * [PolicyResult.Matched] containing [CaptureParameters] used to alter the request. */ - suspend fun apply(content: DisplayContentModel): CaptureParameters? + suspend fun check(content: DisplayContentModel): PolicyResult + + /** The result of a screen capture policy check. */ + sealed interface PolicyResult { + /** The policy rules matched the given display content and will be applied. */ + data class Matched( + /** The name of the policy rule which matched. */ + val policy: String, + /** Why the policy matched. */ + val reason: String, + /** Details on how to modify the screen capture request. */ + val parameters: CaptureParameters, + ) : PolicyResult + + /** The policy rules do not match the given display content and do not apply. */ + data class NotMatched( + /** The name of the policy rule which matched. */ + val policy: String, + /** Why the policy did not match. */ + val reason: String + ) : PolicyResult + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt index 6ca2e9d6d5e0..0ef5207cad37 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt @@ -21,10 +21,10 @@ import android.graphics.Rect /** What to capture */ sealed interface CaptureType { /** Capture the entire screen contents. */ - class FullScreen(val displayId: Int) : CaptureType + data class FullScreen(val displayId: Int) : CaptureType /** Capture the contents of the task only. */ - class IsolatedTask( + data class IsolatedTask( val taskId: Int, val taskBounds: Rect?, ) : CaptureType diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt index 2c0a0dbf8ea9..80aa0efb0a19 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt @@ -16,9 +16,12 @@ package com.android.systemui.screenshot.policy +import android.app.ActivityTaskManager.RootTaskInfo +import android.app.WindowConfiguration import android.content.ComponentName import android.graphics.Bitmap import android.graphics.Rect +import android.os.Process.myUserHandle import android.os.UserHandle import android.util.Log import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN @@ -27,7 +30,10 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.screenshot.ImageCapture import com.android.systemui.screenshot.ScreenshotData import com.android.systemui.screenshot.ScreenshotRequestProcessor +import com.android.systemui.screenshot.data.model.DisplayContentModel import com.android.systemui.screenshot.data.repository.DisplayContentRepository +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched import com.android.systemui.screenshot.policy.CaptureType.FullScreen import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask import kotlinx.coroutines.CoroutineDispatcher @@ -39,8 +45,14 @@ private const val TAG = "PolicyRequestProcessor" class PolicyRequestProcessor( @Background private val background: CoroutineDispatcher, private val capture: ImageCapture, + /** Provides information about the tasks on a given display */ private val displayTasks: DisplayContentRepository, + /** The list of policies to apply, in order of priority */ private val policies: List<CapturePolicy>, + /** The owner to assign for screenshot when a focused task isn't visible */ + private val defaultOwner: UserHandle = myUserHandle(), + /** The assigned component when no application has focus, or not visible */ + private val defaultComponent: ComponentName, ) : ScreenshotRequestProcessor { override suspend fun process(original: ScreenshotData): ScreenshotData { if (original.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) { @@ -48,29 +60,26 @@ class PolicyRequestProcessor( Log.i(TAG, "Screenshot bitmap provided. No modifications applied.") return original } - - val tasks = displayTasks.getDisplayContent(original.displayId) + val displayContent = displayTasks.getDisplayContent(original.displayId) // If policies yield explicit modifications, apply them and return the result Log.i(TAG, "Applying policy checks....") - policies - .firstNotNullOfOrNull { policy -> policy.apply(tasks) } - ?.let { - Log.i(TAG, "Modifying screenshot: $it") - return apply(it, original) + policies.map { policy -> + when (val result = policy.check(displayContent)) { + is Matched -> { + Log.i(TAG, "$result") + return modify(original, result.parameters) + } + is NotMatched -> Log.i(TAG, "$result") } + } // Otherwise capture normally, filling in additional information as needed. - return replaceWithScreenshot( - original = original, - componentName = original.topComponent ?: tasks.rootTasks.firstOrNull()?.topActivity, - owner = original.userHandle, - displayId = original.displayId - ) + return captureScreenshot(original, displayContent) } /** Produce a new [ScreenshotData] using [CaptureParameters] */ - suspend fun apply(updates: CaptureParameters, original: ScreenshotData): ScreenshotData { + suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData { // Update and apply bitmap capture depending on the parameters. val updated = when (val type = updates.type) { @@ -93,6 +102,26 @@ class PolicyRequestProcessor( return updated } + private suspend fun captureScreenshot( + original: ScreenshotData, + displayContent: DisplayContentModel, + ): ScreenshotData { + // The first root task on the display, excluding Picture-in-Picture + val topMainRootTask = + if (!displayContent.systemUiState.shadeExpanded) { + displayContent.rootTasks.firstOrNull(::nonPipVisibleTask) + } else { + null // Otherwise attributed to SystemUI / current user + } + + return replaceWithScreenshot( + original = original, + componentName = topMainRootTask?.topActivity ?: defaultComponent, + owner = topMainRootTask?.userId?.let { UserHandle.of(it) } ?: defaultOwner, + displayId = original.displayId + ) + } + suspend fun replaceWithTaskSnapshot( original: ScreenshotData, componentName: ComponentName?, @@ -100,6 +129,7 @@ class PolicyRequestProcessor( taskId: Int, taskBounds: Rect?, ): ScreenshotData { + Log.i(TAG, "Capturing task snapshot: $componentName / $owner") val taskSnapshot = capture.captureTask(taskId) return original.copy( type = TAKE_SCREENSHOT_PROVIDED_IMAGE, @@ -117,6 +147,7 @@ class PolicyRequestProcessor( owner: UserHandle?, displayId: Int, ): ScreenshotData { + Log.i(TAG, "Capturing screenshot: $componentName / $owner") val screenshot = captureDisplay(displayId) return original.copy( type = TAKE_SCREENSHOT_FULLSCREEN, @@ -127,6 +158,16 @@ class PolicyRequestProcessor( ) } + /** Filter for the task used to attribute a full screen capture to an owner */ + private fun nonPipVisibleTask(info: RootTaskInfo): Boolean { + return info.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED && + info.isVisible && + info.isRunning && + info.numActivities > 0 && + info.topActivity != null && + info.childTaskIds.isNotEmpty() + } + /** TODO: Move to ImageCapture (existing function is non-suspending) */ private suspend fun captureDisplay(displayId: Int): Bitmap? { return withContext(background) { capture.captureDisplay(displayId) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt index 221e64782894..6373a58d9d54 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt @@ -20,10 +20,13 @@ import android.os.UserHandle import com.android.systemui.screenshot.data.model.DisplayContentModel import com.android.systemui.screenshot.data.model.ProfileType import com.android.systemui.screenshot.data.repository.ProfileTypeRepository +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched import com.android.systemui.screenshot.policy.CaptureType.FullScreen import javax.inject.Inject -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.firstOrNull + +private const val POLICY_NAME = "PrivateProfile" /** * Condition: When any visible task belongs to a private user. @@ -35,7 +38,12 @@ class PrivateProfilePolicy constructor( private val profileTypes: ProfileTypeRepository, ) : CapturePolicy { - override suspend fun apply(content: DisplayContentModel): CaptureParameters? { + override suspend fun check(content: DisplayContentModel): PolicyResult { + // The systemUI notification shade isn't a private profile app, skip. + if (content.systemUiState.shadeExpanded) { + return NotMatched(policy = POLICY_NAME, reason = "Notification shade is expanded") + } + // Find the first visible rootTaskInfo with a child task owned by a private user val (rootTask, childTask) = content.rootTasks @@ -48,13 +56,20 @@ constructor( } ?.let { root to it } } - ?: return null + ?: return NotMatched( + policy = POLICY_NAME, + reason = "No private profile tasks are visible" + ) // If matched, return parameters needed to modify the request. - return CaptureParameters( - type = FullScreen(content.displayId), - component = childTask.componentName ?: rootTask.topActivity, - owner = UserHandle.of(childTask.userId), + return Matched( + policy = POLICY_NAME, + reason = "At least one private profile task is visible", + CaptureParameters( + type = FullScreen(content.displayId), + component = childTask.componentName ?: rootTask.topActivity, + owner = UserHandle.of(childTask.userId), + ) ) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt index d2f4d9e039f6..3789371d7c33 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt @@ -18,12 +18,10 @@ package com.android.systemui.screenshot.policy import android.app.ActivityTaskManager.RootTaskInfo import com.android.systemui.screenshot.data.model.ChildTaskModel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.map -internal fun RootTaskInfo.childTasksTopDown(): Flow<ChildTaskModel> { - return ((numActivities - 1) downTo 0).asFlow().map { index -> +/** The child tasks of A RootTaskInfo as [ChildTaskModel] in top-down (z-index ascending) order. */ +internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> { + return ((childTaskIds.size - 1) downTo 0).asSequence().map { index -> ChildTaskModel( childTaskIds[index], childTaskNames[index], @@ -32,16 +30,3 @@ internal fun RootTaskInfo.childTasksTopDown(): Flow<ChildTaskModel> { ) } } - -internal suspend fun RootTaskInfo.firstChildTaskOrNull( - filter: suspend (Int) -> Boolean -): Pair<RootTaskInfo, Int>? { - // Child tasks are provided in bottom-up order - // Filtering is done top-down, so iterate backwards here. - for (index in numActivities - 1 downTo 0) { - if (filter(index)) { - return (this to index) - } - } - return null -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt index 63d15087d12c..a6b01e748246 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt @@ -16,8 +16,13 @@ package com.android.systemui.screenshot.policy +import android.content.ComponentName +import android.content.Context +import android.os.Process import com.android.systemui.Flags.screenshotPrivateProfile +import com.android.systemui.SystemUIService import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.screenshot.ImageCapture import com.android.systemui.screenshot.RequestProcessor @@ -60,6 +65,7 @@ interface ScreenshotPolicyModule { @Provides @SysUISingleton fun bindScreenshotRequestProcessor( + @Application context: Context, @Background background: CoroutineDispatcher, imageCapture: ImageCapture, policyProvider: Provider<ScreenshotPolicy>, @@ -68,10 +74,13 @@ interface ScreenshotPolicyModule { ): ScreenshotRequestProcessor { return if (screenshotPrivateProfile()) { PolicyRequestProcessor( - background, - imageCapture, - displayContentRepoProvider.get(), - policyListProvider.get() + background = background, + capture = imageCapture, + displayTasks = displayContentRepoProvider.get(), + policies = policyListProvider.get(), + defaultOwner = Process.myUserHandle(), + defaultComponent = + ComponentName(context.packageName, SystemUIService::class.java.toString()) ) } else { RequestProcessor(imageCapture, policyProvider.get()) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt index d6b5d6dfda25..689cc11ac10e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt @@ -21,10 +21,14 @@ import android.os.UserHandle import com.android.systemui.screenshot.data.model.DisplayContentModel import com.android.systemui.screenshot.data.model.ProfileType import com.android.systemui.screenshot.data.repository.ProfileTypeRepository +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult +import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask import javax.inject.Inject import kotlinx.coroutines.flow.first +private const val POLICY_NAME = "WorkProfile" + /** * Condition: When the top visible task (excluding PIP mode) belongs to a work user. * @@ -35,7 +39,12 @@ class WorkProfilePolicy constructor( private val profileTypes: ProfileTypeRepository, ) : CapturePolicy { - override suspend fun apply(content: DisplayContentModel): CaptureParameters? { + override suspend fun check(content: DisplayContentModel): PolicyResult { + // The systemUI notification shade isn't a work app, skip. + if (content.systemUiState.shadeExpanded) { + return NotMatched(policy = POLICY_NAME, reason = "Notification shade is expanded") + } + // Find the first non PiP rootTask with a top child task owned by a work user val (rootTask, childTask) = content.rootTasks @@ -44,13 +53,20 @@ constructor( .firstOrNull { (_, child) -> profileTypes.getProfileType(child.userId) == ProfileType.WORK } - ?: return null + ?: return NotMatched( + policy = POLICY_NAME, + reason = "The top-most non-PINNED task does not belong to a work profile user" + ) // If matched, return parameters needed to modify the request. - return CaptureParameters( - type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds), - component = childTask.componentName ?: rootTask.topActivity, - owner = UserHandle.of(childTask.userId), + return PolicyResult.Matched( + policy = POLICY_NAME, + reason = "The top-most non-PINNED task ($childTask) belongs to a work profile user", + CaptureParameters( + type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds), + component = childTask.componentName ?: rootTask.topActivity, + owner = UserHandle.of(childTask.userId), + ) ) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt index 32e9296107a3..d9a51029d346 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt @@ -65,7 +65,9 @@ object ScreenshotShelfViewBinder { } launch { viewModel.actions.collect { actions -> - if (actions.isNotEmpty()) { + val visibleActions = actions.filter { it.visible } + + if (visibleActions.isNotEmpty()) { view .requireViewById<View>(R.id.actions_container_background) .visibility = View.VISIBLE @@ -75,7 +77,7 @@ object ScreenshotShelfViewBinder { // any new actions and update any that are already there. // This assumes that actions can never change order and that each action // ID is unique. - val newIds = actions.map { it.id } + val newIds = visibleActions.map { it.id } for (view in actionsContainer.children.toList()) { if (view.tag !in newIds) { @@ -83,7 +85,7 @@ object ScreenshotShelfViewBinder { } } - for ((index, action) in actions.withIndex()) { + for ((index, action) in visibleActions.withIndex()) { val currentView: View? = actionsContainer.getChildAt(index) if (action.id == currentView?.tag) { // Same ID, update the display diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt index 64b0105a98a0..c5fa8db953fa 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.screenshot.ui.viewmodel data class ActionButtonViewModel( val appearance: ActionButtonAppearance, val id: Int, + val visible: Boolean, val onClicked: (() -> Unit)?, ) { companion object { @@ -29,6 +30,6 @@ data class ActionButtonViewModel( fun withNextId( appearance: ActionButtonAppearance, onClicked: (() -> Unit)? - ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), onClicked) + ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), true, onClicked) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt index fa3480343ea0..f67ad402a738 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt @@ -48,12 +48,34 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager return action.id } + fun setActionVisibility(actionId: Int, visible: Boolean) { + val actionList = _actions.value.toMutableList() + val index = actionList.indexOfFirst { it.id == actionId } + if (index >= 0) { + actionList[index] = + ActionButtonViewModel( + actionList[index].appearance, + actionId, + visible, + actionList[index].onClicked + ) + _actions.value = actionList + } else { + Log.w(TAG, "Attempted to update unknown action id $actionId") + } + } + fun updateActionAppearance(actionId: Int, appearance: ActionButtonAppearance) { val actionList = _actions.value.toMutableList() val index = actionList.indexOfFirst { it.id == actionId } if (index >= 0) { actionList[index] = - ActionButtonViewModel(appearance, actionId, actionList[index].onClicked) + ActionButtonViewModel( + appearance, + actionId, + actionList[index].visible, + actionList[index].onClicked + ) _actions.value = actionList } else { Log.w(TAG, "Attempted to update unknown action id $actionId") diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index f6b1bcc0ceea..f418e7e0278f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -23,7 +23,13 @@ import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.OnBackPressedDispatcherOwner +import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.compose.ui.platform.ComposeView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting import com.android.systemui.communal.dagger.Communal @@ -33,6 +39,7 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor @@ -40,6 +47,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch /** * Controller that's responsible for the glanceable hub container view and its touch handling. @@ -139,13 +147,33 @@ constructor( ): View { return initView( ComposeView(context).apply { - setContent { - PlatformTheme { - CommunalContainer( - viewModel = communalViewModel, - dataSourceDelegator = dataSourceDelegator, - dialogFactory = dialogFactory, - ) + repeatWhenAttached { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + setViewTreeOnBackPressedDispatcherOwner( + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher = + OnBackPressedDispatcher().apply { + setOnBackInvokedDispatcher( + viewRootImpl.onBackInvokedDispatcher + ) + } + + override val lifecycle: Lifecycle = + this@repeatWhenAttached.lifecycle + } + ) + + setContent { + PlatformTheme { + CommunalContainer( + viewModel = communalViewModel, + dataSourceDelegator = dataSourceDelegator, + dialogFactory = dialogFactory, + ) + } + } + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index ebebbe65d54b..8c15817898a8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -124,7 +124,6 @@ constructor( // release focus immediately to kick off focus change transition notificationShadeWindowController.setNotificationShadeFocusable(false) notificationStackScrollLayout.cancelExpandHelper() - sceneInteractor.changeScene(Scenes.Shade, "ShadeController.animateExpandShade") if (delayed) { scope.launch { delay(125) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index bb6ee24b0ffe..f8193a4a1b93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -57,6 +57,7 @@ import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.ContrastColorUtil; +import com.android.systemui.Flags; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.NotificationContentDescription; import com.android.systemui.statusbar.notification.NotificationDozeHelper; @@ -208,6 +209,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi initializeDecorColor(); reloadDimens(); maybeUpdateIconScaleDimens(); + + if (Flags.statusBarMonochromeIconsFix()) { + setCropToPadding(true); + } } /** Should always be preceded by {@link #reloadDimens()} */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 9ce38db1aebe..8b673c951b94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -49,7 +49,6 @@ import android.os.SystemClock; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; -import android.util.ArraySet; import android.view.ContentInfo; import androidx.annotation.NonNull; @@ -150,10 +149,8 @@ public final class NotificationEntry extends ListEntry { private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; private InflationTask mRunningTask = null; - private Throwable mDebugThrowable; public CharSequence remoteInputTextWhenReset; public long lastRemoteInputSent = NOT_LAUNCHED_YET; - public final ArraySet<Integer> mActiveAppOps = new ArraySet<>(3); private final MutableStateFlow<CharSequence> mHeadsUpStatusBarText = StateFlowKt.MutableStateFlow(null); @@ -190,11 +187,6 @@ public final class NotificationEntry extends ListEntry { private boolean mBlockable; /** - * The {@link SystemClock#elapsedRealtime()} when this notification entry was created. - */ - public long mCreationElapsedRealTime; - - /** * Whether this notification has ever been a non-sticky HUN. */ private boolean mIsDemoted = false; @@ -264,13 +256,8 @@ public final class NotificationEntry extends ListEntry { mKey = sbn.getKey(); setSbn(sbn); setRanking(ranking); - mCreationElapsedRealTime = SystemClock.elapsedRealtime(); } - @VisibleForTesting - public void setCreationElapsedRealTime(long time) { - mCreationElapsedRealTime = time; - } @Override public NotificationEntry getRepresentativeEntry() { return this; @@ -581,19 +568,6 @@ public final class NotificationEntry extends ListEntry { return mRunningTask; } - /** - * Set a throwable that is used for debugging - * - * @param debugThrowable the throwable to save - */ - public void setDebugThrowable(Throwable debugThrowable) { - mDebugThrowable = debugThrowable; - } - - public Throwable getDebugThrowable() { - return mDebugThrowable; - } - public void onRemoteInputInserted() { lastRemoteInputSent = NOT_LAUNCHED_YET; remoteInputTextWhenReset = null; @@ -749,12 +723,6 @@ public final class NotificationEntry extends ListEntry { return row != null && row.areChildrenExpanded(); } - - //TODO: probably less confusing to say "is group fully visible" - public boolean isGroupNotFullyVisible() { - return row == null || row.isGroupNotFullyVisible(); - } - public NotificationGuts getGuts() { if (row != null) return row.getGuts(); return null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt index d6118a0b3865..d3c874ce04d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row +import android.app.Flags import android.app.Notification import android.app.Notification.MessagingStyle import android.app.Person @@ -131,7 +132,7 @@ internal object SingleLineViewInflater { val senderName = systemUiContext.resources.getString( R.string.conversation_single_line_name_display, - name + if (Flags.cleanUpSpansAndNewLines()) name?.toString() else name ) // We need to find back-up values for those texts if they are needed and empty 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 0486ef5bca51..13e36d58f6a9 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 @@ -350,7 +350,8 @@ constructor( * * When the shade is expanding, the position is controlled by... the shade. */ - val bounds: StateFlow<NotificationContainerBounds> = + val bounds: StateFlow<NotificationContainerBounds> by lazy { + SceneContainerFlag.assertInLegacyMode() combine( isOnLockscreenWithoutShade, keyguardInteractor.notificationContainerBounds, @@ -380,6 +381,7 @@ constructor( initialValue = NotificationContainerBounds(), ) .dumpValue("bounds") + } /** * Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 37646aea86e2..8ab1eca98cc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -479,7 +479,7 @@ constructor( val runnable = Runnable { assistManagerLazy.get().hideAssist() - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.addFlags(flags) val result = intArrayOf(ActivityManager.START_CANCELED) activityTransitionAnimator.startIntentWithAnimation( 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 5f26702ad867..077f330ff3b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -67,12 +67,12 @@ import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardWmStateRefactor; import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor; +import com.android.systemui.keyguard.shared.RefactorKeyguardDismissIntent; import com.android.systemui.keyguard.shared.model.DismissAction; import com.android.systemui.keyguard.shared.model.KeyguardDone; import com.android.systemui.keyguard.shared.model.KeyguardState; @@ -381,7 +381,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb Lazy<ShadeController> shadeController, LatencyTracker latencyTracker, KeyguardSecurityModel keyguardSecurityModel, - FeatureFlags featureFlags, PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor, PrimaryBouncerInteractor primaryBouncerInteractor, BouncerView primaryBouncerView, @@ -413,7 +412,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mShadeController = shadeController; mLatencyTracker = latencyTracker; mKeyguardSecurityModel = keyguardSecurityModel; - mFlags = featureFlags; mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor; mPrimaryBouncerInteractor = primaryBouncerInteractor; mPrimaryBouncerView = primaryBouncerView; @@ -805,7 +803,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void dismissWithAction(OnDismissAction r, Runnable cancelAction, boolean afterKeyguardGone, String message) { - if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (RefactorKeyguardDismissIntent.isEnabled()) { if (r == null) { return; } @@ -857,7 +855,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return; } - if (!mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (!RefactorKeyguardDismissIntent.isEnabled()) { mAfterKeyguardGoneAction = r; mKeyguardGoneCancelAction = cancelAction; mDismissActionWillAnimateOnKeyguard = r != null @@ -925,7 +923,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * Adds a {@param runnable} to be executed after Keyguard is gone. */ public void addAfterKeyguardGoneRunnable(Runnable runnable) { - if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (RefactorKeyguardDismissIntent.isEnabled()) { if (runnable != null) { mKeyguardDismissActionInteractor.get().runAfterKeyguardGone(runnable); } @@ -1118,7 +1116,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // We update the state (which will show the keyguard) only if an animation will run on // the keyguard. If there is no animation, we wait before updating the state so that we // go directly from bouncer to launcher/app. - if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (RefactorKeyguardDismissIntent.isEnabled()) { if (mKeyguardDismissActionInteractor.get().runDismissAnimationOnKeyguard()) { updateStates(); } @@ -1245,7 +1243,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } private void executeAfterKeyguardGoneAction() { - if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) { + if (RefactorKeyguardDismissIntent.isEnabled()) { return; } if (mAfterKeyguardGoneAction != null) { @@ -1634,7 +1632,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb pw.println(" bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing()); pw.println(" Registered KeyguardViewManagerCallbacks:"); pw.println(" refactorKeyguardDismissIntent enabled:" - + mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)); + + RefactorKeyguardDismissIntent.isEnabled()); for (KeyguardViewManagerCallback callback : mCallbacks) { pw.println(" " + callback); } 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 a30660645990..11cbc9c66923 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -176,7 +176,7 @@ class HeadsUpManagerLogger @Inject constructor( bool1 = alert bool2 = hasEntry }, { - "request: update notification $str1 alert: $bool1 hasEntry: $bool2 reason: $str2" + "request: update notification $str1 alert: $bool1 hasEntry: $bool2" }) } @@ -186,7 +186,7 @@ class HeadsUpManagerLogger @Inject constructor( bool1 = alert bool2 = hasEntry }, { - "update notification $str1 alert: $bool1 hasEntry: $bool2 reason: $str2" + "update notification $str1 alert: $bool1 hasEntry: $bool2" }) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt index 8f18aa8021ae..8ce3b1fa1e73 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt @@ -41,12 +41,14 @@ import kotlinx.coroutines.flow.map interface AncSliceRepository { /** - * ANC slice with a given width. Emits null when there is no ANC slice available. This can mean - * that: + * ANC slice with a given width. [isCollapsed] slice shows a single button, and expanded shows a + * row buttons. + * + * Emits null when there is no ANC slice available. This can mean that: * - there is no supported device connected; * - there is no slice provider for the uri; */ - fun ancSlice(width: Int): Flow<Slice?> + fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> } @OptIn(ExperimentalCoroutinesApi::class) @@ -60,9 +62,14 @@ constructor( private val localMediaRepository = mediaRepositoryFactory.create(null) - override fun ancSlice(width: Int): Flow<Slice?> { + override fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> { return localMediaRepository.currentConnectedDevice - .map { (it as? BluetoothMediaDevice)?.cachedDevice?.device?.getExtraControlUri(width) } + .map { + (it as? BluetoothMediaDevice) + ?.cachedDevice + ?.device + ?.getExtraControlUri(width, isCollapsed, hideLabel) + } .distinctUntilChanged() .flatMapLatest { sliceUri -> sliceUri ?: return@flatMapLatest flowOf(null) @@ -71,7 +78,11 @@ constructor( .flowOn(backgroundCoroutineContext) } - private fun BluetoothDevice.getExtraControlUri(width: Int): Uri? { + private fun BluetoothDevice.getExtraControlUri( + width: Int, + isCollapsed: Boolean, + hideLabel: Boolean + ): Uri? { val uri: String? = BluetoothUtils.getControlUriMetaData(this) uri ?: return null @@ -81,7 +92,8 @@ constructor( Uri.parse( "$uri$width" + "&version=${SliceParameters.VERSION}" + - "&is_collapsed=${SliceParameters.IS_COLLAPSED}" + "&is_collapsed=$isCollapsed" + + "&hide_label=$hideLabel" ) } } @@ -98,11 +110,5 @@ constructor( * 2) new slice */ const val VERSION = 2 - - /** - * Collapsed slice shows a single button, and expanded shows a row buttons. Supported since - * [VERSION]==2. - */ - const val IS_COLLAPSED = false } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt index 89b927480783..dc4be2696204 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.anc.domain import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor +import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria import javax.inject.Inject @@ -31,5 +32,6 @@ constructor( private val ancSliceInteractor: AncSliceInteractor, ) : ComponentAvailabilityCriteria { - override fun isAvailable(): Flow<Boolean> = ancSliceInteractor.ancSlice.map { it != null } + override fun isAvailable(): Flow<Boolean> = + ancSliceInteractor.ancSlices.map { it is AncSlices.Ready } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt index 91af622074a0..cefa26907710 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt @@ -20,16 +20,19 @@ import android.app.slice.Slice.HINT_ERROR import android.app.slice.SliceItem.FORMAT_SLICE import androidx.slice.Slice import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository +import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope 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.SharedFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn /** Provides a valid slice from [AncSliceRepository]. */ @OptIn(ExperimentalCoroutinesApi::class) @@ -41,25 +44,35 @@ constructor( scope: CoroutineScope, ) { - // Start with a positive width to check is the Slice is available. - private val width = MutableStateFlow(1) + // Any positive width to check if the Slice is available. + private val buttonSliceWidth = MutableStateFlow(1) + private val popupSliceWidth = MutableStateFlow(1) - /** Provides a valid ANC slice. */ - val ancSlice: SharedFlow<Slice?> = - width - .flatMapLatest { width -> ancSliceRepository.ancSlice(width) } - .map { slice -> - if (slice?.isValidSlice() == true) { - slice + val ancSlices: StateFlow<AncSlices> = + combine( + buttonSliceWidth.flatMapLatest { + ancSlice(width = it, isCollapsed = true, hideLabel = true) + }, + popupSliceWidth.flatMapLatest { + ancSlice(width = it, isCollapsed = false, hideLabel = false) + } + ) { buttonSlice, popupSlice -> + if (buttonSlice != null && popupSlice != null) { + AncSlices.Ready(buttonSlice = buttonSlice, popupSlice = popupSlice) } else { - null + AncSlices.Unavailable } } - .shareIn(scope, SharingStarted.Eagerly, replay = 1) + .stateIn(scope, SharingStarted.Eagerly, AncSlices.Unavailable) - /** Updates the width of the [ancSlice] */ - fun changeWidth(newWidth: Int) { - width.value = newWidth + /** + * Provides a valid [isCollapsed] ANC slice for a given [width]. Use [hideLabel] == true to + * remove the labels from the [Slice]. + */ + private fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> { + return ancSliceRepository + .ancSlice(width = width, isCollapsed = isCollapsed, hideLabel = hideLabel) + .filter { it?.isValidSlice() != false } } private fun Slice.isValidSlice(): Boolean { @@ -73,4 +86,20 @@ constructor( } return false } + + /** + * Call this to update [AncSlices.Ready.popupSlice] width in a reaction to container size + * change. + */ + fun onPopupSliceWidthChanged(width: Int) { + popupSliceWidth.tryEmit(width) + } + + /** + * Call this to update [AncSlices.Ready.buttonSlice] width in a reaction to container size + * change. + */ + fun onButtonSliceWidthChanged(width: Int) { + buttonSliceWidth.tryEmit(width) + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/model/AncSlices.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/model/AncSlices.kt new file mode 100644 index 000000000000..3cd4e672ce56 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/model/AncSlices.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.anc.domain.model + +import androidx.slice.Slice + +/** Modes current ANC slices state */ +sealed interface AncSlices { + + data class Ready( + val popupSlice: Slice, + val buttonSlice: Slice, + ) : AncSlices + + /** Couldn't one or both slices. */ + data object Unavailable : AncSlices +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt index eb96f6cad8f2..bee79bb68141 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt @@ -16,52 +16,56 @@ package com.android.systemui.volume.panel.component.anc.ui.viewmodel -import android.content.Context import androidx.slice.Slice -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.res.R +import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor -import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel +import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** Volume Panel ANC component view model. */ +@OptIn(ExperimentalCoroutinesApi::class) @VolumePanelScope class AncViewModel @Inject constructor( - @Application private val context: Context, @VolumePanelScope private val coroutineScope: CoroutineScope, private val interactor: AncSliceInteractor, + private val availabilityCriteria: AncAvailabilityCriteria, ) { + val isAvailable: Flow<Boolean> + get() = availabilityCriteria.isAvailable() + /** ANC [Slice]. Null when there is no slice available for ANC. */ - val slice: StateFlow<Slice?> = - interactor.ancSlice.stateIn(coroutineScope, SharingStarted.Eagerly, null) + val popupSlice: StateFlow<Slice?> = + interactor.ancSlices + .filterIsInstance<AncSlices.Ready>() + .map { it.popupSlice } + .stateIn(coroutineScope, SharingStarted.Eagerly, null) - /** - * ButtonViewModel to be shown in the VolumePanel. Null when there is no ANC Slice available. - */ - val button: StateFlow<ButtonViewModel?> = - interactor.ancSlice - .map { slice -> - slice?.let { - ButtonViewModel( - Icon.Resource(R.drawable.ic_noise_aware, null), - context.getString(R.string.volume_panel_noise_control_title) - ) - } - } + /** Button [Slice] to be shown in the VolumePanel. Null when there is no ANC Slice available. */ + val buttonSlice: StateFlow<Slice?> = + interactor.ancSlices + .filterIsInstance<AncSlices.Ready>() + .map { it.buttonSlice } .stateIn(coroutineScope, SharingStarted.Eagerly, null) - /** Call this to update [slice] width in a reaction to container size change. */ - fun changeSliceWidth(width: Int) { - interactor.changeWidth(width) + /** Call this to update [popupSlice] width in a reaction to container size change. */ + fun onPopupSliceWidthChanged(width: Int) { + interactor.onPopupSliceWidthChanged(width) + } + + /** Call this to update [buttonSlice] width in a reaction to container size change. */ + fun onButtonSliceWidthChanged(width: Int) { + interactor.onButtonSliceWidthChanged(width) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt deleted file mode 100644 index ecd89eab1d4b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.panel.component.volume.domain.interactor - -import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope -import javax.inject.Inject - -/** Converts from slider value to volume and back. */ -@VolumePanelScope -class VolumeSliderInteractor @Inject constructor() { - - /** mimic percentage volume setting */ - private val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f - - /** - * Translates [volume], that belongs to [volumeRange] to the value that belongs to - * [displayValueRange]. - */ - fun processVolumeToValue( - volume: Int, - volumeRange: ClosedRange<Int>, - ): Float { - val currentRangeStart: Float = volumeRange.start.toFloat() - val targetRangeStart: Float = displayValueRange.start - val currentRangeLength: Float = (volumeRange.endInclusive.toFloat() - currentRangeStart) - val targetRangeLength: Float = displayValueRange.endInclusive - targetRangeStart - if (currentRangeLength == 0f || targetRangeLength == 0f) { - return 0f - } - val volumeFraction: Float = (volume.toFloat() - currentRangeStart) / currentRangeLength - return targetRangeStart + volumeFraction * targetRangeLength - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index 57b5d570fbbd..1ae1ebb99f8b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -24,7 +24,6 @@ import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.common.shared.model.Icon import com.android.systemui.res.R -import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -44,7 +43,6 @@ constructor( @Assisted private val coroutineScope: CoroutineScope, private val context: Context, private val audioVolumeInteractor: AudioVolumeInteractor, - private val volumeSliderInteractor: VolumeSliderInteractor, ) : SliderViewModel { private val audioStream = audioStreamWrapper.audioStream @@ -105,10 +103,6 @@ constructor( return State( value = volume.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), - valueText = - SliderViewModel.formatValue( - volumeSliderInteractor.processVolumeToValue(volume, volumeRange) - ), icon = getIcon(ringerMode), label = labelsByStream[audioStream]?.let(context::getString) ?: error("No label for the stream: $audioStream"), @@ -157,7 +151,6 @@ constructor( override val valueRange: ClosedFloatingPointRange<Float>, override val icon: Icon, override val label: String, - override val valueText: String, override val disabledMessage: String?, override val isEnabled: Boolean, override val a11yStep: Int, diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt index 8d8fa17bf986..3689303ab28d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt @@ -22,7 +22,6 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.res.R import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession -import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -41,7 +40,6 @@ constructor( @Assisted private val coroutineScope: CoroutineScope, private val context: Context, private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, - private val volumeSliderInteractor: VolumeSliderInteractor, ) : SliderViewModel { override val slider: StateFlow<SliderState> = @@ -66,13 +64,6 @@ constructor( value = currentVolume.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), icon = Icon.Resource(R.drawable.ic_cast, null), - valueText = - SliderViewModel.formatValue( - volumeSliderInteractor.processVolumeToValue( - volume = currentVolume, - volumeRange = volumeRange, - ) - ), label = context.getString(R.string.media_device_cast), isEnabled = true, a11yStep = 1 @@ -83,13 +74,13 @@ constructor( override val value: Float, override val valueRange: ClosedFloatingPointRange<Float>, override val icon: Icon, - override val valueText: String, override val label: String, override val isEnabled: Boolean, override val a11yStep: Int, ) : SliderState { override val disabledMessage: String? get() = null + override val isMutable: Boolean get() = false } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt index 8eb0b8947c37..d71a9d8dae94 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt @@ -28,7 +28,6 @@ sealed interface SliderState { val valueRange: ClosedFloatingPointRange<Float> val icon: Icon? val isEnabled: Boolean - val valueText: String val label: String /** * A11y slider controls works by adjusting one step up or down. The default slider step isn't @@ -42,7 +41,6 @@ sealed interface SliderState { override val value: Float = 0f override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f override val icon: Icon? = null - override val valueText: String = "" override val label: String = "" override val disabledMessage: String? = null override val a11yStep: Int = 0 diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt index e78f833086f8..74aee559194b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt @@ -26,9 +26,4 @@ interface SliderViewModel { fun onValueChanged(state: SliderState, newValue: Float) fun toggleMuted(state: SliderState) - - companion object { - - fun formatValue(value: Float): String = "%.0f".format(value) - } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 9a833114ab72..f32d5b8838b7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -142,7 +142,6 @@ class ClockEventControllerTest : SysuiTestCase() { context.resources, context, mainExecutor, - IMMEDIATE, bgExecutor, clockBuffers, withDeps.featureFlags, @@ -362,27 +361,6 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() = - runBlocking(IMMEDIATE) { - val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN)) - .thenReturn(transitionStep) - - val job = underTest.listenForAnyStateToLockscreenTransition(this) - transitionStep.value = - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.LOCKSCREEN, - transitionState = TransitionState.STARTED, - ) - yield() - - verify(animations, times(2)).doze(0f) - - job.cancel() - } - - @Test fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) @@ -400,27 +378,6 @@ class ClockEventControllerTest : SysuiTestCase() { verify(animations, never()).doze(1f) - job.cancel() - } - - @Test - fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() = - runBlocking(IMMEDIATE) { - val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN)) - .thenReturn(transitionStep) - - val job = underTest.listenForAnyStateToLockscreenTransition(this) - transitionStep.value = - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - transitionState = TransitionState.STARTED, - ) - yield() - - verify(animations, never()).doze(0f) - job.cancel() } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index fde45d34a4fd..5af0c1f9765d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -52,6 +52,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -157,7 +158,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.internal.util.reflection.FieldSetter; @@ -809,6 +809,31 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void whenFaceAuthenticated_biometricAuthenticatedCallback_beforeUpdatingFpState() { + // GIVEN listening for UDFPS fingerprint + when(mAuthController.isUdfpsSupported()).thenReturn(true); + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); + mTestableLooper.processAllMessages(); + keyguardIsVisible(); + final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal); + mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel; + + // WHEN face is authenticated + when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true); + when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true); + when(mFaceAuthInteractor.isLockedOut()).thenReturn(false); + mKeyguardUpdateMonitor.onFaceAuthenticated(0, true); + mTestableLooper.processAllMessages(); + + // THEN verify keyguardUpdateMonitorCallback receives an onAuthenticated callback + // before cancelling the fingerprint request + InOrder inOrder = inOrder(mTestCallback, fpCancel); + inOrder.verify(mTestCallback).onBiometricAuthenticated( + eq(0), eq(BiometricSourceType.FACE), eq(true)); + inOrder.verify(fpCancel).cancel(); + } + + @Test public void whenDetectFingerprint_biometricDetectCallback() { ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor = ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class); @@ -2133,7 +2158,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { null /* trustGrantedMessages */); // THEN onTrustChanged is called FIRST - final InOrder inOrder = Mockito.inOrder(callback); + final InOrder inOrder = inOrder(callback); inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId())); // AND THEN onTrustGrantedForCurrentUser callback called 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 33a6010d816c..8f3fed74fe97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.biometrics +import android.app.ActivityTaskManager import android.app.admin.DevicePolicyManager import android.content.pm.PackageManager import android.hardware.biometrics.BiometricAuthenticator @@ -44,9 +45,12 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor @@ -120,10 +124,12 @@ open class AuthContainerViewTest : SysuiTestCase() { lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var activityTaskManager: ActivityTaskManager private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val biometricPromptRepository = FakePromptRepository() + private val biometricStatusRepository = FakeBiometricStatusRepository() private val fingerprintRepository = FakeFingerprintPropertyRepository() private val displayStateRepository = FakeDisplayStateRepository() private val credentialInteractor = FakeCredentialInteractor() @@ -143,6 +149,7 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var displayRepository: FakeDisplayRepository private lateinit var displayStateInteractor: DisplayStateInteractor private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor + private lateinit var biometricStatusInteractor: BiometricStatusInteractor private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) @@ -168,6 +175,8 @@ open class AuthContainerViewTest : SysuiTestCase() { selectedUserInteractor, testScope.backgroundScope, ) + biometricStatusInteractor = + BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) // Set up default logo icon whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) context.setMockPackageManager(packageManager) @@ -645,6 +654,7 @@ open class AuthContainerViewTest : SysuiTestCase() { promptSelectorInteractor, context, udfpsOverlayInteractor, + biometricStatusInteractor, udfpsUtils ), { credentialViewModel }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt index 99c2c4076403..aff93bd339ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt @@ -465,10 +465,34 @@ private val ROTATION_90_INPUTS = nativeYOutsideSensor = 150f, ) -/* ROTATION_180 is not supported. It's treated the same as ROTATION_0. */ +/* + * ROTATION_180 map: + * _ _ _ _ + * _ _ s _ + * _ _ s _ + * _ _ _ _ + * _ O _ _ + * _ _ _ _ + * + * (_) empty space + * (S) sensor + * (O) touch outside of the sensor + */ +private val ROTATION_180_NATIVE_SENSOR_BOUNDS = + Rect( + 200, /* left */ + 100, /* top */ + 300, /* right */ + 300, /* bottom */ + ) private val ROTATION_180_INPUTS = - ROTATION_0_INPUTS.copy( + OrientationBasedInputs( rotation = Surface.ROTATION_180, + nativeOrientation = (ORIENTATION - Math.PI.toFloat() / 2), + nativeXWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterX(), + nativeYWithinSensor = ROTATION_180_NATIVE_SENSOR_BOUNDS.exactCenterY(), + nativeXOutsideSensor = 150f, + nativeYOutsideSensor = 450f, ) /* @@ -639,33 +663,6 @@ private fun genPositiveTestCases( } } -private fun genTestCasesForUnsupportedAction( - motionEventAction: Int -): List<SinglePointerTouchProcessorTest.TestCase> { - val isGoodOverlap = true - val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID_1) - return previousPointerOnSensorIds.map { previousPointerOnSensorId -> - val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f) - val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap) - val nativeY = ROTATION_0_INPUTS.getNativeY(isGoodOverlap) - val event = - MOTION_EVENT.copy( - action = motionEventAction, - x = nativeX, - y = nativeY, - minor = NATIVE_MINOR, - major = NATIVE_MAJOR, - ) - SinglePointerTouchProcessorTest.TestCase( - event = event, - currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = isGoodOverlap)), - previousPointerOnSensorId = previousPointerOnSensorId, - overlayParams = overlayParams, - expected = TouchProcessorResult.Failure(), - ) - } -} - private fun obtainMotionEvent( action: Int, pointerId: Int, 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 5b0df5d05703..a6c7f728ff3d 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 @@ -16,12 +16,14 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.app.ActivityTaskManager import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Point import android.graphics.drawable.BitmapDrawable +import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT import android.hardware.biometrics.PromptContentItemBulletedText import android.hardware.biometrics.PromptContentView @@ -40,9 +42,12 @@ import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.UdfpsUtils +import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor +import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor @@ -51,6 +56,7 @@ 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 +import com.android.systemui.biometrics.shared.model.AuthenticationReason import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.DisplayRotation @@ -59,6 +65,7 @@ import com.android.systemui.biometrics.shared.model.toSensorType import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.display.data.repository.FakeDisplayRepository +import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor @@ -102,6 +109,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo + @Mock private lateinit var activityTaskManager: ActivityTaskManager private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val testScope = TestScope() @@ -115,9 +123,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository private lateinit var promptRepository: FakePromptRepository private lateinit var displayStateRepository: FakeDisplayStateRepository + private lateinit var biometricStatusRepository: FakeBiometricStatusRepository private lateinit var displayRepository: FakeDisplayRepository private lateinit var displayStateInteractor: DisplayStateInteractor private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor + private lateinit var biometricStatusInteractor: BiometricStatusInteractor private lateinit var selector: PromptSelectorInteractor private lateinit var viewModel: PromptViewModel @@ -157,6 +167,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa selectedUserInteractor, testScope.backgroundScope ) + biometricStatusRepository = FakeBiometricStatusRepository() + biometricStatusInteractor = + BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository) selector = PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) selector.resetPrompt() @@ -178,6 +191,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa selector, mContext, udfpsOverlayInteractor, + biometricStatusInteractor, udfpsUtils ) iconViewModel = viewModel.iconViewModel @@ -1053,8 +1067,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun auto_confirm_authentication_when_finger_down() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) - // No icon button when face only, can't confirm before auth - if (!testCase.isFaceOnly) { + if (testCase.isCoex) { viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN)) } viewModel.showAuthenticated(testCase.authenticatedModality, 0) @@ -1069,14 +1082,18 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(canTryAgain).isFalse() assertThat(authenticated?.isAuthenticated).isTrue() - if (testCase.isFaceOnly && expectConfirmation) { - assertThat(size).isEqualTo(PromptSize.MEDIUM) - assertButtonsVisible( - cancel = true, - confirm = true, - ) + if (expectConfirmation) { + if (testCase.isFaceOnly) { + assertThat(size).isEqualTo(PromptSize.MEDIUM) + assertButtonsVisible( + cancel = true, + confirm = true, + ) - viewModel.confirmAuthenticated() + viewModel.confirmAuthenticated() + } else if (testCase.isCoex) { + assertThat(authenticated?.isAuthenticatedAndConfirmed).isTrue() + } assertThat(message).isEqualTo(PromptMessage.Empty) assertButtonsVisible() } @@ -1086,8 +1103,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) - // No icon button when face only, can't confirm before auth - if (!testCase.isFaceOnly) { + if (testCase.isCoex) { viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN)) viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP)) } @@ -1413,6 +1429,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa packageName = packageName, ) + biometricStatusRepository.setFingerprintAcquiredStatus( + AcquiredFingerprintAuthenticationStatus( + AuthenticationReason.BiometricPromptAuthentication, + BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_UNKNOWN + ) + ) // put the view model in the initial authenticating state, unless explicitly skipped val startMode = when { 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 11ec417fc5f8..318227f2d1a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -91,6 +91,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.ui.viewmodel.DreamViewModel; import com.android.systemui.dump.DumpManager; @@ -219,6 +220,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock CoroutineDispatcher mDispatcher; private @Mock DreamViewModel mDreamViewModel; + private @Mock CommunalTransitionViewModel mCommunalTransitionViewModel; private @Mock SystemPropertiesHelper mSystemPropertiesHelper; private @Mock SceneContainerFlags mSceneContainerFlags; @@ -241,6 +243,10 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { .thenReturn(mock(Flow.class)); when(mDreamViewModel.getTransitionEnded()) .thenReturn(mock(Flow.class)); + when(mCommunalTransitionViewModel.getShowByDefault()) + .thenReturn(mock(Flow.class)); + when(mCommunalTransitionViewModel.getTransitionFromOccludedEnded()) + .thenReturn(mock(Flow.class)); when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId); when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( @@ -1229,6 +1235,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mSystemClock, mDispatcher, () -> mDreamViewModel, + () -> mCommunalTransitionViewModel, mSystemPropertiesHelper, () -> mock(WindowManagerLockscreenVisibilityManager.class), mSelectedUserInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt index d75cbec8c542..d52e911d31f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt @@ -21,7 +21,10 @@ import androidx.test.filters.SmallTest import com.android.keyguard.ClockEventController import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.res.R import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth @@ -49,6 +52,7 @@ class KeyguardClockRepositoryTest : SysuiTestCase() { private lateinit var fakeSettings: FakeSettings @Mock private lateinit var clockRegistry: ClockRegistry @Mock private lateinit var clockEventController: ClockEventController + private val fakeFeatureFlagsClassic = FakeFeatureFlagsClassic() @Before fun setup() { @@ -63,7 +67,9 @@ class KeyguardClockRepositoryTest : SysuiTestCase() { clockRegistry, clockEventController, dispatcher, - scope.backgroundScope + scope.backgroundScope, + context, + fakeFeatureFlagsClassic, ) } @@ -82,4 +88,12 @@ class KeyguardClockRepositoryTest : SysuiTestCase() { val value = collectLastValue(underTest.selectedClockSize) Truth.assertThat(value()).isEqualTo(SettingsClockSize.DYNAMIC) } + + @Test + fun testShouldForceSmallClock() = + scope.runTest { + overrideResource(R.bool.force_small_clock_on_lockscreen, true) + fakeFeatureFlagsClassic.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true) + Truth.assertThat(underTest.shouldForceSmallClock).isTrue() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt index b6b457142a3f..4270236f761e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt @@ -27,16 +27,12 @@ import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRe import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.clocks.ClockConfig import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.res.R import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -103,72 +99,6 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun composeLockscreenOff_DoesAppliesSplitShadeWeatherClockBlueprint() { - testScope.runTest { - val blueprint by collectLastValue(underTest.blueprint) - whenever(clockController.config) - .thenReturn( - ClockConfig( - id = "DIGITAL_CLOCK_WEATHER", - name = "clock", - description = "clock", - ) - ) - clockRepository.setCurrentClock(clockController) - overrideResource(R.bool.config_use_split_notification_shade, true) - configurationRepository.onConfigurationChange() - runCurrent() - - assertThat(blueprint?.id).isNotEqualTo(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID) - } - } - - @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testDoesAppliesSplitShadeWeatherClockBlueprint() { - testScope.runTest { - val blueprint by collectLastValue(underTest.blueprint) - whenever(clockController.config) - .thenReturn( - ClockConfig( - id = "DIGITAL_CLOCK_WEATHER", - name = "clock", - description = "clock", - ) - ) - clockRepository.setCurrentClock(clockController) - overrideResource(R.bool.config_use_split_notification_shade, true) - configurationRepository.onConfigurationChange() - runCurrent() - - assertThat(blueprint?.id).isEqualTo(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID) - } - } - - @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testAppliesWeatherClockBlueprint() { - testScope.runTest { - val blueprint by collectLastValue(underTest.blueprint) - whenever(clockController.config) - .thenReturn( - ClockConfig( - id = "DIGITAL_CLOCK_WEATHER", - name = "clock", - description = "clock", - ) - ) - clockRepository.setCurrentClock(clockController) - overrideResource(R.bool.config_use_split_notification_shade, false) - configurationRepository.onConfigurationChange() - runCurrent() - - assertThat(blueprint?.id).isEqualTo(WEATHER_CLOCK_BLUEPRINT_ID) - } - } - - @Test @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) fun testDoesNotApplySplitShadeBlueprint() { testScope.runTest { 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 2c0a518350da..e155a1b52595 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 @@ -27,6 +27,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dock.DockManager +import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeCommandQueue @@ -108,6 +110,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { private val powerInteractor = kosmos.powerInteractor private val communalInteractor = kosmos.communalInteractor + private val dockManager = kosmos.fakeDockManager @Before fun setUp() { @@ -1230,6 +1233,38 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun occludedToGlanceableHubWhenDocked() = + testScope.runTest { + // GIVEN a device on lockscreen + keyguardRepository.setKeyguardShowing(true) + runCurrent() + + // GIVEN a prior transition has run to OCCLUDED + runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // GIVEN device is docked + dockManager.setIsDocked(true) + dockManager.setDockEvent(DockManager.STATE_DOCKED) + + // WHEN occlusion ends + keyguardRepository.setKeyguardOccluded(false) + runCurrent() + + // THEN a transition to GLANCEABLE_HUB should occur + assertThat(transitionRepository) + .startedTransition( + ownerName = FromOccludedTransitionInteractor::class.simpleName, + from = KeyguardState.OCCLUDED, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNotNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test fun occludedToAlternateBouncer() = testScope.runTest { // GIVEN a prior transition has run to OCCLUDED diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index b5f668cef08c..4f2b690f9fcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -38,7 +38,6 @@ import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope @@ -86,7 +85,6 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { { mock(DeviceEntryBackgroundViewModel::class.java) }, { falsingManager }, { mock(VibratorHelper::class.java) }, - mock(CoroutineDispatcher::class.java), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt index 7b5dd1fc6c7a..85a20e22818f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -12,191 +12,244 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * */ package com.android.systemui.keyguard.ui.viewmodel -import android.provider.Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.filters.SmallTest -import com.android.keyguard.ClockEventController -import com.android.keyguard.KeyguardClockSwitch.LARGE -import com.android.keyguard.KeyguardClockSwitch.SMALL +import com.android.keyguard.KeyguardClockSwitch +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.KeyguardClockRepository -import com.android.systemui.keyguard.data.repository.KeyguardClockRepositoryImpl -import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.shared.ComposeLockscreen +import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository +import com.android.systemui.keyguard.data.repository.keyguardClockRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.shared.model.SettingsClockSize +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.res.R -import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.shared.model.ShadeMode -import com.android.systemui.shared.clocks.ClockRegistry -import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor +import com.android.systemui.testKosmos import com.android.systemui.util.Utils import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlin.test.Test -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.Mock -import org.mockito.MockitoAnnotations +import org.mockito.Mockito.mock @SmallTest @RunWith(JUnit4::class) class KeyguardClockViewModelTest : SysuiTestCase() { - private lateinit var scheduler: TestCoroutineScheduler - private lateinit var dispatcher: CoroutineDispatcher - private lateinit var scope: TestScope + private lateinit var kosmos: Kosmos private lateinit var underTest: KeyguardClockViewModel - private lateinit var keyguardInteractor: KeyguardInteractor - private lateinit var keyguardRepository: KeyguardRepository - private lateinit var keyguardClockInteractor: KeyguardClockInteractor - private lateinit var keyguardClockRepository: KeyguardClockRepository - private lateinit var fakeSettings: FakeSettings - private val shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single) - @Mock private lateinit var clockRegistry: ClockRegistry - @Mock private lateinit var clock: ClockController - @Mock private lateinit var largeClock: ClockFaceController - @Mock private lateinit var clockFaceConfig: ClockFaceConfig - @Mock private lateinit var eventController: ClockEventController - @Mock private lateinit var notifsKeyguardInteractor: NotificationsKeyguardInteractor - @Mock private lateinit var areNotificationsFullyHidden: Flow<Boolean> - @Mock private lateinit var shadeInteractor: ShadeInteractor + private lateinit var testScope: TestScope + private lateinit var clockController: ClockController + private lateinit var config: ClockFaceConfig @Before fun setup() { - MockitoAnnotations.initMocks(this) - KeyguardInteractorFactory.create().let { - keyguardInteractor = it.keyguardInteractor - keyguardRepository = it.repository - } - fakeSettings = FakeSettings() - scheduler = TestCoroutineScheduler() - dispatcher = StandardTestDispatcher(scheduler) - scope = TestScope(dispatcher) - setupMockClock() - keyguardClockRepository = - KeyguardClockRepositoryImpl( - fakeSettings, - clockRegistry, - eventController, - dispatcher, - scope.backgroundScope - ) - keyguardClockInteractor = KeyguardClockInteractor(keyguardClockRepository) - whenever(notifsKeyguardInteractor.areNotificationsFullyHidden) - .thenReturn(areNotificationsFullyHidden) - whenever(shadeInteractor.shadeMode).thenReturn(shadeMode) - underTest = - KeyguardClockViewModel( - keyguardInteractor, - keyguardClockInteractor, - scope.backgroundScope, - notifsKeyguardInteractor, - shadeInteractor, - ) + kosmos = testKosmos() + testScope = kosmos.testScope + underTest = kosmos.keyguardClockViewModel + + clockController = mock(ClockController::class.java) + val largeClock = mock(ClockFaceController::class.java) + config = mock(ClockFaceConfig::class.java) + + whenever(clockController.largeClock).thenReturn(largeClock) + whenever(largeClock.config).thenReturn(config) } @Test - fun testClockSize_alwaysSmallClock() = - scope.runTest { - // When use double line clock is disabled, - // should always return small - fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 0) - keyguardClockRepository.setClockSize(LARGE) - val value = collectLastValue(underTest.clockSize) - assertThat(value()).isEqualTo(SMALL) + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun currentClockLayout_splitShadeOn_clockCentered_largeClock() = + testScope.runTest { + with(kosmos) { + shadeRepository.setShadeMode(ShadeMode.Split) + keyguardRepository.setClockShouldBeCentered(true) + keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + } + val currentClockLayout by collectLastValue(underTest.currentClockLayout) + assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK) + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() = + testScope.runTest { + with(kosmos) { + shadeRepository.setShadeMode(ShadeMode.Split) + keyguardRepository.setClockShouldBeCentered(false) + keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + } + val currentClockLayout by collectLastValue(underTest.currentClockLayout) + assertThat(currentClockLayout) + .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK) } @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() = + testScope.runTest { + with(kosmos) { + shadeRepository.setShadeMode(ShadeMode.Split) + keyguardRepository.setClockShouldBeCentered(false) + keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL) + } + val currentClockLayout by collectLastValue(underTest.currentClockLayout) + assertThat(currentClockLayout) + .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_SMALL_CLOCK) + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun currentClockLayout_singleShade_smallClock_smallClock() = + testScope.runTest { + with(kosmos) { + shadeRepository.setShadeMode(ShadeMode.Single) + keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL) + } + val currentClockLayout by collectLastValue(underTest.currentClockLayout) + assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.SMALL_CLOCK) + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun currentClockLayout_singleShade_largeClock_largeClock() = + testScope.runTest { + with(kosmos) { + shadeRepository.setShadeMode(ShadeMode.Single) + keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + } + val currentClockLayout by collectLastValue(underTest.currentClockLayout) + assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK) + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun hasCustomPositionUpdatedAnimation_withConfigTrue_isTrue() = + testScope.runTest { + with(kosmos) { + keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + whenever(config.hasCustomPositionUpdatedAnimation).thenReturn(true) + fakeKeyguardClockRepository.setCurrentClock(clockController) + } + + val hasCustomPositionUpdatedAnimation by + collectLastValue(underTest.hasCustomPositionUpdatedAnimation) + assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(true) + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun hasCustomPositionUpdatedAnimation_withConfigFalse_isFalse() = + testScope.runTest { + with(kosmos) { + keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + + whenever(config.hasCustomPositionUpdatedAnimation).thenReturn(false) + fakeKeyguardClockRepository.setCurrentClock(clockController) + } + + val hasCustomPositionUpdatedAnimation by + collectLastValue(underTest.hasCustomPositionUpdatedAnimation) + assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(false) + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun testClockSize_alwaysSmallClockSize() = + testScope.runTest { + kosmos.fakeKeyguardClockRepository.setSelectedClockSize(SettingsClockSize.SMALL) + kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + + val value by collectLastValue(underTest.clockSize) + assertThat(value).isEqualTo(KeyguardClockSwitch.SMALL) + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) fun testClockSize_dynamicClockSize() = - scope.runTest { - fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) - keyguardClockRepository.setClockSize(SMALL) - var value = collectLastValue(underTest.clockSize) - assertThat(value()).isEqualTo(SMALL) - - keyguardClockRepository.setClockSize(LARGE) - value = collectLastValue(underTest.clockSize) - assertThat(value()).isEqualTo(LARGE) + testScope.runTest { + kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL) + kosmos.fakeKeyguardClockRepository.setSelectedClockSize(SettingsClockSize.DYNAMIC) + val value by collectLastValue(underTest.clockSize) + assertThat(value).isEqualTo(KeyguardClockSwitch.SMALL) + + kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + assertThat(value).isEqualTo(KeyguardClockSwitch.LARGE) } @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) fun isLargeClockVisible_whenLargeClockSize_isTrue() = - scope.runTest { - fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) - keyguardClockRepository.setClockSize(LARGE) - var value = collectLastValue(underTest.isLargeClockVisible) - assertThat(value()).isEqualTo(true) + testScope.runTest { + kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) + val value by collectLastValue(underTest.isLargeClockVisible) + assertThat(value).isEqualTo(true) } @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) fun isLargeClockVisible_whenSmallClockSize_isFalse() = - scope.runTest { - fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) - keyguardClockRepository.setClockSize(SMALL) - var value = collectLastValue(underTest.isLargeClockVisible) - assertThat(value()).isEqualTo(false) + testScope.runTest { + kosmos.keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL) + val value by collectLastValue(underTest.isLargeClockVisible) + assertThat(value).isEqualTo(false) } @Test - fun testSmallClockTop_splitshade() = - scope.runTest { - shadeMode.value = ShadeMode.Split - if (!ComposeLockscreen.isEnabled) { - assertThat(underTest.getSmallClockTopMargin(context)) - .isEqualTo( - context.resources.getDimensionPixelSize( - R.dimen.keyguard_split_shade_top_margin - ) - ) - } else { - assertThat(underTest.getSmallClockTopMargin(context)) - .isEqualTo( - context.resources.getDimensionPixelSize( - R.dimen.keyguard_split_shade_top_margin - ) - Utils.getStatusBarHeaderHeightKeyguard(context) - ) - } + @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun testSmallClockTop_splitShade_composeLockscreenOn() = + testScope.runTest { + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + assertThat(underTest.getSmallClockTopMargin(context)) + .isEqualTo( + context.resources.getDimensionPixelSize( + R.dimen.keyguard_split_shade_top_margin + ) - Utils.getStatusBarHeaderHeightKeyguard(context) + ) } @Test - fun testSmallClockTop_nonSplitshade() = - scope.runTest { - if (!ComposeLockscreen.isEnabled) { - assertThat(underTest.getSmallClockTopMargin(context)) - .isEqualTo( - context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + - Utils.getStatusBarHeaderHeightKeyguard(context) - ) - } else { - assertThat(underTest.getSmallClockTopMargin(context)) - .isEqualTo( - context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) - ) - } + @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun testSmallClockTop_splitShade_composeLockscreenOff() = + testScope.runTest { + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + assertThat(underTest.getSmallClockTopMargin(context)) + .isEqualTo( + context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) + ) } - private fun setupMockClock() { - whenever(clock.largeClock).thenReturn(largeClock) - whenever(largeClock.config).thenReturn(clockFaceConfig) - whenever(clockFaceConfig.hasCustomWeatherDataDisplay).thenReturn(false) - whenever(clockRegistry.createCurrentClock()).thenReturn(clock) - } + @Test + @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun testSmallClockTop_nonSplitShade_composeLockscreenOn() = + testScope.runTest { + assertThat(underTest.getSmallClockTopMargin(context)) + .isEqualTo( + context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + ) + } + + @Test + @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun testSmallClockTop_nonSplitShade_composeLockscreenOff() = + testScope.runTest { + assertThat(underTest.getSmallClockTopMargin(context)) + .isEqualTo( + context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + + Utils.getStatusBarHeaderHeightKeyguard(context) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt deleted file mode 100644 index d12980a74a18..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.keyguard.ui.viewmodel - -import androidx.test.filters.SmallTest -import com.android.keyguard.KeyguardClockSwitch -import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository -import com.android.systemui.keyguard.data.repository.keyguardClockRepository -import com.android.systemui.keyguard.data.repository.keyguardRepository -import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.clocks.ClockController -import com.android.systemui.plugins.clocks.ClockFaceConfig -import com.android.systemui.plugins.clocks.ClockFaceController -import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.shade.shared.model.ShadeMode -import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat -import kotlin.test.Test -import kotlinx.coroutines.test.runTest -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.Mockito.mock - -@SmallTest -@RunWith(JUnit4::class) -class KeyguardClockViewModelWithKosmosTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val underTest = kosmos.keyguardClockViewModel - private val testScope = kosmos.testScope - - @Test - fun currentClockLayout_splitShadeOn_clockCentered_largeClock() = - testScope.runTest { - with(kosmos) { - shadeRepository.setShadeMode(ShadeMode.Split) - keyguardRepository.setClockShouldBeCentered(true) - keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) - } - val currentClockLayout by collectLastValue(underTest.currentClockLayout) - assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK) - } - - @Test - fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() = - testScope.runTest { - with(kosmos) { - shadeRepository.setShadeMode(ShadeMode.Split) - keyguardRepository.setClockShouldBeCentered(false) - keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) - } - val currentClockLayout by collectLastValue(underTest.currentClockLayout) - assertThat(currentClockLayout) - .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK) - } - - @Test - fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() = - testScope.runTest { - with(kosmos) { - shadeRepository.setShadeMode(ShadeMode.Split) - keyguardRepository.setClockShouldBeCentered(false) - keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL) - } - val currentClockLayout by collectLastValue(underTest.currentClockLayout) - assertThat(currentClockLayout) - .isEqualTo(KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_SMALL_CLOCK) - } - - @Test - fun currentClockLayout_singleShade_smallClock_smallClock() = - testScope.runTest { - with(kosmos) { - shadeRepository.setShadeMode(ShadeMode.Single) - keyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL) - } - val currentClockLayout by collectLastValue(underTest.currentClockLayout) - assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.SMALL_CLOCK) - } - - @Test - fun currentClockLayout_singleShade_largeClock_largeClock() = - testScope.runTest { - with(kosmos) { - shadeRepository.setShadeMode(ShadeMode.Single) - keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) - } - val currentClockLayout by collectLastValue(underTest.currentClockLayout) - assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK) - } - - @Test - fun hasCustomPositionUpdatedAnimation_withConfigTrue_isTrue() = - testScope.runTest { - with(kosmos) { - keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) - fakeKeyguardClockRepository.setCurrentClock( - buildClockController(hasCustomPositionUpdatedAnimation = true) - ) - } - - val hasCustomPositionUpdatedAnimation by - collectLastValue(underTest.hasCustomPositionUpdatedAnimation) - assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(true) - } - - @Test - fun hasCustomPositionUpdatedAnimation_withConfigFalse_isFalse() = - testScope.runTest { - with(kosmos) { - keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE) - fakeKeyguardClockRepository.setCurrentClock( - buildClockController(hasCustomPositionUpdatedAnimation = false) - ) - } - - val hasCustomPositionUpdatedAnimation by - collectLastValue(underTest.hasCustomPositionUpdatedAnimation) - assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(false) - } - - private fun buildClockController( - hasCustomPositionUpdatedAnimation: Boolean = false - ): ClockController { - val clockController = mock(ClockController::class.java) - val largeClock = mock(ClockFaceController::class.java) - val config = mock(ClockFaceConfig::class.java) - - whenever(clockController.largeClock).thenReturn(largeClock) - whenever(largeClock.config).thenReturn(config) - whenever(config.hasCustomPositionUpdatedAnimation) - .thenReturn(hasCustomPositionUpdatedAnimation) - - return clockController - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt index bde821b79469..853e50a12ea5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt @@ -16,8 +16,6 @@ package com.android.systemui.screenshot -import android.app.Notification -import android.app.PendingIntent import android.content.Intent import android.net.Uri import android.os.Process @@ -27,8 +25,6 @@ import android.view.accessibility.AccessibilityManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase -import com.android.systemui.clipboardoverlay.EditTextActivity -import com.android.systemui.res.R import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -41,10 +37,7 @@ import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.runner.RunWith import org.mockito.Mockito.verifyNoMoreInteractions -import org.mockito.kotlin.any -import org.mockito.kotlin.never import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) @SmallTest @@ -52,7 +45,6 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { private val actionExecutor = mock<ActionExecutor>() private val accessibilityManager = mock<AccessibilityManager>() private val uiEventLogger = mock<UiEventLogger>() - private val smartActionsProvider = mock<SmartActionsProvider>() private val request = ScreenshotData.forTesting() private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0) @@ -119,42 +111,10 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER) } - @Test - fun quickShareTapped_wrapsAndSendsIntent() = runTest { - val quickShare = - Notification.Action( - R.drawable.ic_screenshot_edit, - "TestQuickShare", - PendingIntent.getActivity( - context, - 0, - Intent(context, EditTextActivity::class.java), - PendingIntent.FLAG_MUTABLE - ) - ) - whenever(smartActionsProvider.requestQuickShare(any(), any(), any())).then { - (it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare) - } - whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer { - (it.getArgument(0) as Notification.Action).actionIntent - } - actionsProvider = createActionsProvider() - - viewModel.actions.value[2].onClicked?.invoke() - verify(uiEventLogger, never()) - .log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), any(), any()) - verify(smartActionsProvider, never()).wrapIntent(any(), any(), any(), any()) - actionsProvider.setCompletedScreenshot(validResult) - verify(smartActionsProvider) - .wrapIntent(eq(quickShare), eq(validResult.uri), eq(validResult.subject), eq("testid")) - verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), eq(0), eq("")) - } - private fun createActionsProvider(): ScreenshotActionsProvider { return DefaultScreenshotActionsProvider( context, viewModel, - smartActionsProvider, uiEventLogger, request, "testid", 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 dfe72cf11dcb..e7b29d826a0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -398,7 +398,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFakeKeyguardRepository = keyguardInteractorDeps.getRepository(); mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository); mFakeKeyguardClockRepository = new FakeKeyguardClockRepository(); - mKeyguardClockInteractor = new KeyguardClockInteractor(mFakeKeyguardClockRepository); + mKeyguardClockInteractor = mKosmos.getKeyguardClockInteractor(); mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor(); mShadeRepository = new FakeShadeRepository(); mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 69e0db9c5e7a..54a6523d82a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -138,7 +138,7 @@ public class NotificationLoggerTest extends SysuiTestCase { mHeadsUpManager, mPowerInteractor, mActiveNotificationsInteractor, - mKosmos.getFakeSceneContainerFlags(), + mKosmos.getSceneContainerFlags(), () -> mKosmos.getSceneInteractor()); mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index fe0d9d06c8f4..db053d842bdb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -23,7 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; -import static com.android.systemui.concurrency.FakeExecutorKosmosKt.getFakeExecutor; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static junit.framework.Assert.assertNotNull; @@ -97,7 +96,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.kotlin.JavaAdapter; -import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.wmshell.BubblesManager; import org.junit.Before; @@ -182,7 +180,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mHeadsUpManager, PowerInteractorFactory.create().getPowerInteractor(), mActiveNotificationsInteractor, - mKosmos.getFakeSceneContainerFlags(), + mKosmos.getSceneContainerFlags(), () -> mKosmos.getSceneInteractor() ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 05fd63e96089..dc3db4c7fa19 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -172,7 +172,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mKeyguardRepository, mCommandQueue, PowerInteractorFactory.create().getPowerInteractor(), - mKosmos.getFakeSceneContainerFlags(), + mKosmos.getSceneContainerFlags(), new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(new FakeConfigurationRepository()), new FakeShadeRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 34605fed7d28..f8c01e7c5275 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -76,8 +76,6 @@ import com.android.systemui.bouncer.ui.BouncerView; import com.android.systemui.bouncer.ui.BouncerViewDelegate; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -145,7 +143,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent; @Mock private DreamOverlayStateController mDreamOverlayStateController; @Mock private LatencyTracker mLatencyTracker; - private FakeFeatureFlags mFeatureFlags; @Mock private KeyguardSecurityModel mKeyguardSecurityModel; @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @@ -188,11 +185,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { .thenReturn(mKeyguardMessageAreaController); when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate); when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback); - mFeatureFlags = new FakeFeatureFlags(); - mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false); mSetFlagsRule.disableFlags( com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, - com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR + com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT ); when(mNotificationShadeWindowController.getWindowRootView()) @@ -218,7 +214,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> mShadeController, mLatencyTracker, mKeyguardSecurityModel, - mFeatureFlags, mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, mBouncerView, @@ -728,7 +723,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { () -> mShadeController, mLatencyTracker, mKeyguardSecurityModel, - mFeatureFlags, mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, mBouncerView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index ae3425678abd..69536c5e9c0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -30,7 +30,7 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory 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.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository @@ -60,7 +60,7 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { keyguardRepository, mock<CommandQueue>(), PowerInteractorFactory.create().powerInteractor, - kosmos.fakeSceneContainerFlags, + kosmos.sceneContainerFlags, FakeKeyguardBouncerRepository(), ConfigurationInteractor(FakeConfigurationRepository()), FakeShadeRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt index 7a83cfe852d6..6f589418cf1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt @@ -23,14 +23,8 @@ import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.model.SysUiStateTest -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_OUT -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.MAIN -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.NOT_PLAYING -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationStateChangedCallback -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.PaintDrawCallback -import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback +import com.android.systemui.surfaceeffects.PaintDrawCallback +import com.android.systemui.surfaceeffects.RenderEffectDrawCallback import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader import com.google.common.truth.Truth.assertThat @@ -50,8 +44,8 @@ class LoadingEffectTest : SysUiStateTest() { var paintFromCallback: Paint? = null val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) { - paintFromCallback = loadingPaint + override fun onDraw(paint: Paint) { + paintFromCallback = paint } } val loadingEffect = @@ -75,8 +69,8 @@ class LoadingEffectTest : SysUiStateTest() { var renderEffectFromCallback: RenderEffect? = null val drawCallback = object : RenderEffectDrawCallback { - override fun onDraw(loadingRenderEffect: RenderEffect) { - renderEffectFromCallback = loadingRenderEffect + override fun onDraw(renderEffect: RenderEffect) { + renderEffectFromCallback = renderEffect } } val loadingEffect = @@ -98,16 +92,19 @@ class LoadingEffectTest : SysUiStateTest() { @Test fun play_animationStateChangesInOrder() { val config = TurbulenceNoiseAnimationConfig() - val states = mutableListOf(NOT_PLAYING) + val states = mutableListOf(LoadingEffect.AnimationState.NOT_PLAYING) val stateChangedCallback = - object : AnimationStateChangedCallback { - override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { states.add(newState) } } val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) {} + override fun onDraw(paint: Paint) {} } val loadingEffect = LoadingEffect( @@ -125,7 +122,14 @@ class LoadingEffectTest : SysUiStateTest() { animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong()) animatorTestRule.advanceTimeBy(500) - assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING) + assertThat(states) + .containsExactly( + LoadingEffect.AnimationState.NOT_PLAYING, + LoadingEffect.AnimationState.EASE_IN, + LoadingEffect.AnimationState.MAIN, + LoadingEffect.AnimationState.EASE_OUT, + LoadingEffect.AnimationState.NOT_PLAYING + ) } @Test @@ -133,16 +137,22 @@ class LoadingEffectTest : SysUiStateTest() { val config = TurbulenceNoiseAnimationConfig() var numPlay = 0 val stateChangedCallback = - object : AnimationStateChangedCallback { - override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { - if (oldState == NOT_PLAYING && newState == EASE_IN) { + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { + if ( + oldState == LoadingEffect.AnimationState.NOT_PLAYING && + newState == LoadingEffect.AnimationState.EASE_IN + ) { numPlay++ } } } val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) {} + override fun onDraw(paint: Paint) {} } val loadingEffect = LoadingEffect( @@ -172,9 +182,15 @@ class LoadingEffectTest : SysUiStateTest() { } var isFinished = false val stateChangedCallback = - object : AnimationStateChangedCallback { - override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { - if (oldState == EASE_OUT && newState == NOT_PLAYING) { + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { + if ( + oldState == LoadingEffect.AnimationState.EASE_OUT && + newState == LoadingEffect.AnimationState.NOT_PLAYING + ) { isFinished = true } } @@ -205,13 +221,19 @@ class LoadingEffectTest : SysUiStateTest() { val config = TurbulenceNoiseAnimationConfig(maxDuration = 1000f) val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) {} + override fun onDraw(paint: Paint) {} } var isFinished = false val stateChangedCallback = - object : AnimationStateChangedCallback { - override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { - if (oldState == MAIN && newState == NOT_PLAYING) { + object : LoadingEffect.AnimationStateChangedCallback { + override fun onStateChanged( + oldState: LoadingEffect.AnimationState, + newState: LoadingEffect.AnimationState + ) { + if ( + oldState == LoadingEffect.AnimationState.MAIN && + newState == LoadingEffect.AnimationState.NOT_PLAYING + ) { isFinished = true } } @@ -242,7 +264,7 @@ class LoadingEffectTest : SysUiStateTest() { ) val drawCallback = object : PaintDrawCallback { - override fun onDraw(loadingPaint: Paint) {} + override fun onDraw(paint: Paint) {} } val loadingEffect = LoadingEffect( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt index 9cbe6337befe..2ae6f542ac3e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.ui.viewmodel import android.content.applicationContext +import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.displayStateInteractor import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor @@ -30,6 +31,7 @@ val Kosmos.promptViewModel by Fixture { promptSelectorInteractor = promptSelectorInteractor, context = applicationContext, udfpsOverlayInteractor = udfpsOverlayInteractor, + biometricStatusInteractor = biometricStatusInteractor, udfpsUtils = udfpsUtils ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 8866fd31faac..4b6ef373a7ee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.widgets.EditWidgetsActivityStarter +import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -33,7 +34,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.activityStarter 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.settings.userTracker import com.android.systemui.smartspace.data.repository.smartspaceRepository import com.android.systemui.user.data.repository.fakeUserRepository @@ -54,11 +55,12 @@ val Kosmos.communalInteractor by Fixture { userTracker = userTracker, activityStarter = activityStarter, userManager = userManager, + dockManager = fakeDockManager, logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), communalSettingsInteractor = communalSettingsInteractor, sceneInteractor = sceneInteractor, - sceneContainerFlags = fakeSceneContainerFlags, + sceneContainerFlags = sceneContainerFlags, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt index b4773f69f1c5..cd2710ef8757 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt @@ -17,17 +17,21 @@ package com.android.systemui.communal.domain.interactor import com.android.systemui.communal.data.repository.communalSettingsRepository +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.settings.userTracker import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.util.mockito.mock val Kosmos.communalSettingsInteractor by Fixture { CommunalSettingsInteractor( bgScope = applicationCoroutineScope, + bgExecutor = fakeExecutor, repository = communalSettingsRepository, userInteractor = selectedUserInteractor, + userTracker = userTracker, tableLogBuffer = mock(), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt index 23967224e43a..e36ddc17e5a8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.communal.ui.viewmodel +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel @@ -27,9 +29,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.communalTransitionViewModel by Kosmos.Fixture { CommunalTransitionViewModel( - glanceableHubToLockscreenTransitionViewModel, - lockscreenToGlanceableHubTransitionViewModel, - dreamingToGlanceableHubTransitionViewModel, - glanceableHubToDreamingTransitionViewModel, + glanceableHubToLockscreenTransitionViewModel = + glanceableHubToLockscreenTransitionViewModel, + lockscreenToGlanceableHubTransitionViewModel = + lockscreenToGlanceableHubTransitionViewModel, + dreamToGlanceableHubTransitionViewModel = dreamingToGlanceableHubTransitionViewModel, + glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel, + communalInteractor = communalInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java index 37540621557f..b99310bcbe38 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFake.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java @@ -49,6 +49,7 @@ public class DockManagerFake implements DockManager { return mDocked; } + /** Sets the docked state */ public void setIsDocked(boolean docked) { mDocked = docked; } @@ -58,6 +59,7 @@ public class DockManagerFake implements DockManager { return false; } + /** Notifies callbacks of dock state change */ public void setDockEvent(int event) { mCallback.onEvent(event); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFakeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFakeKosmos.kt index 06275fa226a5..06275fa226a5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dock/DockManagerFakeKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFakeKosmos.kt diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/BrokenWithSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/BrokenWithSceneContainer.kt new file mode 100644 index 000000000000..29b088b0afc1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/BrokenWithSceneContainer.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.flags + +import com.android.systemui.Flags.FLAG_SCENE_CONTAINER + +/** + * This is used by [SceneContainerRule] to assert that the test is broken when + * [FLAG_SCENE_CONTAINER] is enabled. + */ +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +annotation class BrokenWithSceneContainer(val bugId: Int) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt index c1d2ad6e1be3..e83205c5a859 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt @@ -18,12 +18,14 @@ package com.android.systemui.flags import android.platform.test.annotations.EnableFlags import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN +import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI +import com.android.systemui.Flags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT import com.android.systemui.Flags.FLAG_SCENE_CONTAINER /** @@ -39,6 +41,8 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR, FLAG_PREDICTIVE_BACK_SYSUI, FLAG_SCENE_CONTAINER, + FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, + FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT, ) @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt index d6f2f77ca67a..45ea36464194 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt @@ -34,8 +34,9 @@ val Kosmos.fakeFeatureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) - set(Flags.NSSL_DEBUG_LINES, false) set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) + set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) + set(Flags.NSSL_DEBUG_LINES, false) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerFlagParameterization.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerFlagParameterization.kt new file mode 100644 index 000000000000..4e24233a6681 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerFlagParameterization.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.flags + +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization +import com.android.systemui.Flags.FLAG_SCENE_CONTAINER + +/** The name of the one flag to be disabled for OFF parameterization */ +private const val flagNameToDisable = FLAG_SCENE_CONTAINER + +/** Cache of the flags to be enabled for ON parameterization */ +private val flagNamesToEnable = + EnableSceneContainer::class.java.getAnnotation(EnableFlags::class.java)!!.value.toList() + +/** + * Provides one or two copies of this [FlagsParameterization]; one which disabled + * [FLAG_SCENE_CONTAINER] and if none of the dependencies of it are disabled by this, a second copy + * which enables [FLAG_SCENE_CONTAINER] and all the dependencies (just like [EnableSceneContainer]). + */ +fun FlagsParameterization.andSceneContainer(): Sequence<FlagsParameterization> = sequence { + check(flagNameToDisable !in mOverrides) { + "Can't add $flagNameToDisable to FlagsParameterization: $this" + } + yield(FlagsParameterization(mOverrides + mapOf(flagNameToDisable to false))) + if (flagNamesToEnable.all { mOverrides[it] != false }) { + // Can't add the parameterization of enabling SceneContainerFlag to a parameterization that + // explicitly disables one of the prerequisite flags. + yield(FlagsParameterization(mOverrides + flagNamesToEnable.associateWith { true })) + } +} + +/** + * Doubles (roughly; see below) the given list of [FlagsParameterization] for enabling and disabling + * SceneContainerFlag. + * + * The input parameterization may not define [FLAG_SCENE_CONTAINER]. + * + * Any [FlagsParameterization] which disables any flag that is a dependency of + * [FLAG_SCENE_CONTAINER], will not add a state for enabling, and the state will simply be converted + * to one which disables. Just like [EnableSceneContainer], enabling will also enable all the other + * dependencies. For any flag parameterization where a dependency is disabled, an "enabled" + * parameterization is inconsistent, so it will not be added. + */ +fun List<FlagsParameterization>.andSceneContainer(): List<FlagsParameterization> = + flatMap { it.andSceneContainer() }.toList() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt index 775ad14d4ad9..9ec1481355a5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SceneContainerRule.kt @@ -18,6 +18,7 @@ package com.android.systemui.flags import com.android.systemui.scene.shared.flag.SceneContainerFlag import org.junit.Assert +import org.junit.AssumptionViolatedException import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement @@ -33,10 +34,7 @@ class SceneContainerRule : TestRule { return object : Statement() { @Throws(Throwable::class) override fun evaluate() { - val hasAnnotation = - description?.testClass?.getAnnotation(EnableSceneContainer::class.java) != - null || description?.getAnnotation(EnableSceneContainer::class.java) != null - if (hasAnnotation) { + if (description.hasAnnotation<EnableSceneContainer>()) { Assert.assertTrue( "SceneContainerFlag.isEnabled is false:" + "\n * Did you forget to add a new aconfig flag dependency in" + @@ -45,8 +43,31 @@ class SceneContainerRule : TestRule { SceneContainerFlag.isEnabled ) } + if ( + description.hasAnnotation<BrokenWithSceneContainer>() && + SceneContainerFlag.isEnabled + ) { + runCatching { base?.evaluate() } + .onFailure { exception -> + if (exception is AssumptionViolatedException) { + throw AssertionError( + "This is marked @BrokenWithSceneContainer, but was skipped.", + exception + ) + } + throw AssumptionViolatedException("Test is still broken", exception) + } + throw AssertionError( + "HOORAY! You fixed a test that was marked @BrokenWithSceneContainer. " + + "Remove the obsolete annotation to fix this failure." + ) + } base?.evaluate() } } } + + inline fun <reified T : Annotation> Description?.hasAnnotation(): Boolean = + this?.testClass?.getAnnotation(T::class.java) != null || + this?.getAnnotation(T::class.java) != null } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt index eba5a11cecdb..4f2310f9972d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt @@ -50,14 +50,25 @@ class FakeKeyguardClockRepository @Inject constructor() : KeyguardClockRepositor get() = _previewClock override val clockEventController: ClockEventController get() = mock() + override val shouldForceSmallClock: Boolean + get() = _shouldForceSmallClock + private var _shouldForceSmallClock: Boolean = false override fun setClockSize(@ClockSize size: Int) { _clockSize.value = size } + fun setSelectedClockSize(size: SettingsClockSize) { + selectedClockSize.value = size + } + fun setCurrentClock(clockController: ClockController) { _currentClock.value = clockController } + + fun setShouldForceSmallClock(shouldForceSmallClock: Boolean) { + _shouldForceSmallClock = shouldForceSmallClock + } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt index 75489b617120..8954231a9731 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt @@ -17,8 +17,6 @@ package com.android.systemui.keyguard.data.repository import android.os.fakeExecutorHandler -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID -import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT @@ -34,8 +32,6 @@ val Kosmos.keyguardBlueprintRepository by setOf( defaultBlueprint, splitShadeBlueprint, - weatherClockBlueprint, - splitShadeWeatherClockBlueprint, ), handler = fakeExecutorHandler, assert = mock<ThreadAssert>(), @@ -50,22 +46,6 @@ private val defaultBlueprint = get() = listOf() } -private val weatherClockBlueprint = - object : KeyguardBlueprint { - override val id: String - get() = WEATHER_CLOCK_BLUEPRINT_ID - override val sections: List<KeyguardSection> - get() = listOf() - } - -private val splitShadeWeatherClockBlueprint = - object : KeyguardBlueprint { - override val id: String - get() = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID - override val sections: List<KeyguardSection> - get() = listOf() - } - private val splitShadeBlueprint = object : KeyguardBlueprint { override val id: String diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt index 12165cdc5658..d52883eb38af 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorKosmos.kt @@ -18,6 +18,22 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.keyguardClockRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor val Kosmos.keyguardClockInteractor by - Kosmos.Fixture { KeyguardClockInteractor(keyguardClockRepository) } + Kosmos.Fixture { + KeyguardClockInteractor( + keyguardClockRepository = keyguardClockRepository, + applicationScope = applicationCoroutineScope, + mediaCarouselInteractor = mediaCarouselInteractor, + activeNotificationsInteractor = activeNotificationsInteractor, + shadeInteractor = shadeInteractor, + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + headsUpNotificationInteractor = headsUpNotificationInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt new file mode 100644 index 000000000000..2c6d44f10152 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +val Kosmos.keyguardDismissActionInteractor by + Kosmos.Fixture { + KeyguardDismissActionInteractor( + repository = keyguardRepository, + transitionInteractor = keyguardTransitionInteractor, + dismissInteractor = keyguardDismissInteractor, + applicationScope = testScope.backgroundScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt new file mode 100644 index 000000000000..f33ca95e488d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.data.repository.trustRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.user.domain.interactor.selectedUserInteractor +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +val Kosmos.keyguardDismissInteractor by + Kosmos.Fixture { + KeyguardDismissInteractor( + trustRepository = trustRepository, + keyguardRepository = keyguardRepository, + primaryBouncerInteractor = primaryBouncerInteractor, + alternateBouncerInteractor = alternateBouncerInteractor, + powerInteractor = powerInteractor, + selectedUserInteractor = selectedUserInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt index ffa4133c7269..9774e4aa51a5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsKosmos.kt @@ -19,21 +19,19 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.domain.interactor.keyguardDismissActionInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.sysuiStatusBarStateController -import com.android.systemui.util.mockito.mock import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.bouncerToGoneFlows by Fixture { BouncerToGoneFlows( statusBarStateController = sysuiStatusBarStateController, primaryBouncerInteractor = mockPrimaryBouncerInteractor, - keyguardDismissActionInteractor = mock(), - featureFlags = featureFlagsClassic, + keyguardDismissActionInteractor = { keyguardDismissActionInteractor }, shadeInteractor = shadeInteractor, animationFlow = keyguardTransitionAnimationFlow, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt index 60dd48aeaf61..a048d3cfffca 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -26,7 +25,6 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.notif val Kosmos.keyguardClockViewModel by Kosmos.Fixture { KeyguardClockViewModel( - keyguardInteractor = keyguardInteractor, keyguardClockInteractor = keyguardClockInteractor, applicationScope = applicationCoroutineScope, notifsKeyguardInteractor = notificationsKeyguardInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt index 4ecff73f71ed..d6edea29d6f0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelKosmos.kt @@ -19,20 +19,18 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.keyguard.domain.interactor.keyguardDismissActionInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.statusbar.sysuiStatusBarStateController -import com.android.systemui.util.mockito.mock import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.primaryBouncerToGoneTransitionViewModel by Fixture { PrimaryBouncerToGoneTransitionViewModel( statusBarStateController = sysuiStatusBarStateController, primaryBouncerInteractor = mockPrimaryBouncerInteractor, - keyguardDismissActionInteractor = mock(), - featureFlags = featureFlagsClassic, + keyguardDismissActionInteractor = { keyguardDismissActionInteractor }, bouncerToGoneFlows = bouncerToGoneFlows, animationFlow = keyguardTransitionAnimationFlow, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index afc8f309f6d2..a46d35842cf3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -48,6 +48,7 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.scene.shared.model.sceneDataSource import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor @@ -70,6 +71,7 @@ class KosmosJavaAdapter( val testScope by lazy { kosmos.testScope } val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic } val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } + val sceneContainerFlags by lazy { kosmos.sceneContainerFlags } val fakeExecutor by lazy { kosmos.fakeExecutor } val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler } val configurationRepository by lazy { kosmos.fakeConfigurationRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt index bae5257a98bd..ded725683e59 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt @@ -21,7 +21,7 @@ import dagger.Module import dagger.Provides class FakeSceneContainerFlags( - var enabled: Boolean = false, + var enabled: Boolean = SceneContainerFlag.isEnabled, ) : SceneContainerFlags { override fun isEnabled(): Boolean { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt index b66d7f974eca..d4a72b437fd8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt @@ -24,8 +24,9 @@ class FakeAncSliceRepository : AncSliceRepository { private val sliceByWidth = mutableMapOf<Int, MutableStateFlow<Slice?>>() - override fun ancSlice(width: Int): Flow<Slice?> = - sliceByWidth.getOrPut(width) { MutableStateFlow(null) } + override fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> { + return sliceByWidth.getOrPut(width) { MutableStateFlow(null) } + } fun putSlice(width: Int, slice: Slice?) { sliceByWidth.getOrPut(width) { MutableStateFlow(null) }.value = slice diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java index 741411095f53..0f65544f8b66 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java @@ -100,6 +100,16 @@ public class LongArrayMultiStateCounter_host { mLastStateChangeTimestampMs = timestampMs; } + public void copyStatesFrom(LongArrayMultiStateCounterRavenwood source) { + for (int i = 0; i < mStateCount; i++) { + mStates[i].mTimeInStateSinceUpdate = source.mStates[i].mTimeInStateSinceUpdate; + Arrays.fill(mStates[i].mCounter, 0); + } + mCurrentState = source.mCurrentState; + mLastStateChangeTimestampMs = source.mLastStateChangeTimestampMs; + mLastUpdateTimestampMs = source.mLastUpdateTimestampMs; + } + public void setValue(int state, long[] values) { System.arraycopy(values, 0, mStates[state].mCounter, 0, mArrayLength); } @@ -335,6 +345,10 @@ public class LongArrayMultiStateCounter_host { getInstance(instanceId).setState(state, timestampMs); } + public static void native_copyStatesFrom(long targetInstanceId, long sourceInstanceId) { + getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId)); + } + public static void native_incrementValues(long instanceId, long containerInstanceId, long timestampMs) { getInstance(instanceId).incrementValues( diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md index 7c0cee812996..2ab43bbeaad0 100644 --- a/ravenwood/test-authors.md +++ b/ravenwood/test-authors.md @@ -17,6 +17,7 @@ android_ravenwood_test { name: "MyTestsRavenwood", static_libs: [ "androidx.annotation_annotation", + "androidx.test.ext.junit", "androidx.test.rules", ], srcs: [ @@ -34,7 +35,7 @@ android_ravenwood_test { import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 73584154df3a..8a699ef39280 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1617,9 +1617,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int displayId = displays[i].getDisplayId(); onDisplayRemoved(displayId); } - if (com.android.server.accessibility.Flags.cleanupA11yOverlays()) { detachAllOverlays(); - } } /** diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index bbbc4aef55f3..e64e500c9b65 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -4175,8 +4175,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public void enableShortcutsForTargets( boolean enable, @UserShortcutType int shortcutTypes, @NonNull List<String> shortcutTargets, @UserIdInt int userId) { - mContext.enforceCallingPermission( - Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets"); + if (android.view.accessibility.Flags.migrateEnableShortcuts()) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets"); + } else { + mContext.enforceCallingPermission( + Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets"); + } for (int shortcutType : USER_SHORTCUT_TYPES) { if ((shortcutTypes & shortcutType) == shortcutType) { enableShortcutForTargets(enable, shortcutType, shortcutTargets, userId); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index ce9cdc2aeab5..0353d5a962c7 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1510,46 +1510,74 @@ public class BackupManagerService extends IBackupManager.Stub { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { return; } - dumpWithoutCheckingPermission(fd, pw, args); - } - @VisibleForTesting - void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) { - int userId = binderGetCallingUserId(); - if (!isUserReadyForBackup(userId)) { - pw.println("Inactive"); + int argIndex = 0; + + String op = nextArg(args, argIndex); + argIndex++; + + if ("--help".equals(op)) { + showDumpUsage(pw); return; } - - if (args != null) { - for (String arg : args) { - if ("-h".equals(arg)) { - pw.println("'dumpsys backup' optional arguments:"); - pw.println(" -h : this help text"); - pw.println(" a[gents] : dump information about defined backup agents"); - pw.println(" transportclients : dump information about transport clients"); - pw.println(" transportstats : dump transport statts"); - pw.println(" users : dump the list of users for which backup service " - + "is running"); - return; - } else if ("users".equals(arg.toLowerCase())) { - pw.print(DUMP_RUNNING_USERS_MESSAGE); - for (int i = 0; i < mUserServices.size(); i++) { - pw.print(" " + mUserServices.keyAt(i)); - } - pw.println(); - return; + if ("users".equals(op)) { + pw.print(DUMP_RUNNING_USERS_MESSAGE); + for (int i = 0; i < mUserServices.size(); i++) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), + "dump()"); + if (userBackupManagerService != null) { + pw.print(" " + userBackupManagerService.getUserId()); } } + pw.println(); + return; } - - for (int i = 0; i < mUserServices.size(); i++) { + if ("--user".equals(op)) { + String userArg = nextArg(args, argIndex); + argIndex++; + if (userArg == null) { + showDumpUsage(pw); + return; + } + int userId = UserHandle.parseUserArg(userArg); UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()"); + getServiceForUserIfCallerHasPermission(userId, "dump()"); if (userBackupManagerService != null) { userBackupManagerService.dump(fd, pw, args); } + return; } + if (op == null || "agents".startsWith(op) || "transportclients".equals(op) + || "transportstats".equals(op)) { + for (int i = 0; i < mUserServices.size(); i++) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()"); + if (userBackupManagerService != null) { + userBackupManagerService.dump(fd, pw, args); + } + } + return; + } + + showDumpUsage(pw); + } + + private String nextArg(String[] args, int argIndex) { + if (argIndex >= args.length) { + return null; + } + return args[argIndex]; + } + + private static void showDumpUsage(PrintWriter pw) { + pw.println("'dumpsys backup' optional arguments:"); + pw.println(" --help : this help text"); + pw.println(" a[gents] : dump information about defined backup agents"); + pw.println(" transportclients : dump information about transport clients"); + pw.println(" transportstats : dump transport stats"); + pw.println(" users : dump the list of users for which backup service is running"); + pw.println(" --user <userId> : dump information for user userId"); } /** @@ -1661,7 +1689,7 @@ public class BackupManagerService extends IBackupManager.Stub { * @param message A message to include in the exception if it is thrown. */ void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) { - if (Binder.getCallingUserHandle().getIdentifier() != userId) { + if (binderGetCallingUserId() != userId) { mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 4f46ecdb9228..f98799dd3723 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -124,7 +124,9 @@ import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsDumpHelperImpl; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; -import com.android.server.power.stats.CpuAggregatedPowerStatsProcessor; +import com.android.server.power.stats.CpuPowerStatsProcessor; +import com.android.server.power.stats.MobileRadioPowerStatsProcessor; +import com.android.server.power.stats.PhoneCallPowerStatsProcessor; import com.android.server.power.stats.PowerStatsAggregator; import com.android.server.power.stats.PowerStatsExporter; import com.android.server.power.stats.PowerStatsScheduler; @@ -408,11 +410,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge); final long powerStatsThrottlePeriodCpu = context.getResources().getInteger( com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodCpu); + final long powerStatsThrottlePeriodMobileRadio = context.getResources().getInteger( + com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodMobileRadio); mBatteryStatsConfig = new BatteryStatsImpl.BatteryStatsConfig.Builder() .setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel) .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge) - .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu) + .setPowerStatsThrottlePeriodMillis( + BatteryConsumer.POWER_COMPONENT_CPU, + powerStatsThrottlePeriodCpu) + .setPowerStatsThrottlePeriodMillis( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, + powerStatsThrottlePeriodMobileRadio) .build(); mPowerStatsUidResolver = new PowerStatsUidResolver(); mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, @@ -470,7 +479,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub AggregatedPowerStatsConfig.STATE_SCREEN, AggregatedPowerStatsConfig.STATE_PROCESS_STATE) .setProcessor( - new CpuAggregatedPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies)); + new CpuPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies)); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE) + .setProcessor( + new MobileRadioPowerStatsProcessor(mPowerProfile)); + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE, + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) + .setProcessor(new PhoneCallPowerStatsProcessor()); return config; } @@ -494,8 +516,16 @@ public final class BatteryStatsService extends IBatteryStats.Stub } public void systemServicesReady() { - mStats.setPowerStatsCollectorEnabled(Flags.streamlinedBatteryStats()); - mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(Flags.streamlinedBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CPU, + Flags.streamlinedBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, + Flags.streamlinedConnectivityBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_CPU, + Flags.streamlinedBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, + Flags.streamlinedConnectivityBatteryStats()); mWorker.systemServicesReady(); mStats.systemServicesReady(mContext); mCpuWakeupStats.systemServicesReady(); @@ -536,7 +566,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub * Notifies BatteryStatsService that the system server is ready. */ public void onSystemReady() { - mStats.onSystemReady(); + mStats.onSystemReady(mContext); mPowerStatsScheduler.start(Flags.streamlinedBatteryStats()); } @@ -1591,19 +1621,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long elapsedRealtime = SystemClock.elapsedRealtime(); final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { - final boolean update; synchronized (mStats) { // Ignore if no power state change. if (mLastPowerStateFromRadio == powerState) return; mLastPowerStateFromRadio = powerState; - update = mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid, + mStats.noteMobileRadioPowerStateLocked(powerState, timestampNs, uid, elapsedRealtime, uptime); } - - if (update) { - mWorker.scheduleSync("modem-data", BatteryExternalStatsWorker.UPDATE_RADIO); - } }); } FrameworkStatsLog.write_non_chained( diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 390dca30ac96..3e3ec17eb0e0 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -145,6 +145,7 @@ public class SettingsToPropertiesMapper { "core_experiments_team_internal", "core_graphics", "core_libraries", + "crumpet", "dck_framework", "devoptions_settings", "game", diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 9896d9dd3f48..0e22ef1eb741 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4674,6 +4674,12 @@ public class AudioService extends IAudioService.Stub int streamTypeAlias = mStreamVolumeAlias[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; + if ((streamType == AudioManager.STREAM_VOICE_CALL) + && isInCommunication() && mDeviceBroker.isBluetoothScoActive()) { + Log.i(TAG, "setStreamVolume for STREAM_VOICE_CALL, switching to STREAM_BLUETOOTH_SCO"); + streamType = AudioManager.STREAM_BLUETOOTH_SCO; + } + final int device = (ada == null) ? getDeviceForStream(streamType) : ada.getInternalType(); diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 85a231fafb0a..1c169a055078 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -585,7 +585,7 @@ import javax.xml.datatype.DatatypeConfigurationException; * </point> * </luxThresholds> * </idleScreenRefreshRateTimeout> - * + * <supportsVrr>true</supportsVrr> * * </displayConfiguration> * } @@ -872,6 +872,8 @@ public class DisplayDeviceConfig { */ private float mBrightnessCapForWearBedtimeMode; + private boolean mVrrSupportEnabled; + private final DisplayManagerFlags mFlags; @VisibleForTesting @@ -1606,6 +1608,13 @@ public class DisplayDeviceConfig { return mBrightnessCapForWearBedtimeMode; } + /** + * @return true if display supports dvrr + */ + public boolean isVrrSupportEnabled() { + return mVrrSupportEnabled; + } + @Override public String toString() { return "DisplayDeviceConfig{" @@ -1705,6 +1714,8 @@ public class DisplayDeviceConfig { + "\n" + "mEvenDimmerBrightnessData:" + (mEvenDimmerBrightnessData != null ? mEvenDimmerBrightnessData.toString() : "null") + + "\n" + + "mVrrSupported= " + mVrrSupportEnabled + "\n" + "}"; } @@ -1779,6 +1790,7 @@ public class DisplayDeviceConfig { mHdrBrightnessData = HdrBrightnessData.loadConfig(config); loadBrightnessCapForWearBedtimeMode(config); loadIdleScreenRefreshRateTimeoutConfigs(config); + mVrrSupportEnabled = config.getSupportsVrr(); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 31092f27c838..8f1277bfe507 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2764,6 +2764,7 @@ public final class DisplayManagerService extends SystemService { + requestedRefreshRate + " on Display: " + displayId); } } + mDisplayModeDirector.getAppRequestObserver().setAppRequest( displayId, requestedModeId, requestedMinRefreshRate, requestedMaxRefreshRate); @@ -4939,6 +4940,18 @@ public final class DisplayManagerService extends SystemService { } @Override + public boolean isVrrSupportEnabled(int displayId) { + DisplayDevice device; + synchronized (mSyncRoot) { + device = getDeviceForDisplayLocked(displayId); + } + if (device == null) { + return false; + } + return device.getDisplayDeviceConfig().isVrrSupportEnabled(); + } + + @Override public void setWindowManagerMirroring(int displayId, boolean isMirroring) { synchronized (mSyncRoot) { final DisplayDevice device = getDeviceForDisplayLocked(displayId); diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index d084d1c26b1c..a862b6e8f8f4 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -105,6 +105,7 @@ import java.util.function.IntSupplier; * picked by the system based on system-wide and display-specific configuration. */ public class DisplayModeDirector { + public static final float SYNCHRONIZED_REFRESH_RATE_TARGET = DEFAULT_LOW_REFRESH_RATE; public static final float SYNCHRONIZED_REFRESH_RATE_TOLERANCE = 1; private static final String TAG = "DisplayModeDirector"; @@ -149,6 +150,9 @@ public class DisplayModeDirector { // A map from the display ID to the default mode of that display. private SparseArray<Display.Mode> mDefaultModeByDisplay; + // a map from display id to vrr support + private SparseBooleanArray mVrrSupportedByDisplay; + private BrightnessObserver mBrightnessObserver; private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener; @@ -189,8 +193,6 @@ public class DisplayModeDirector { private final DisplayManagerFlags mDisplayManagerFlags; - private final boolean mDvrrSupported; - public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, @NonNull DisplayManagerFlags displayManagerFlags) { @@ -200,8 +202,6 @@ public class DisplayModeDirector { public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, @NonNull Injector injector, @NonNull DisplayManagerFlags displayManagerFlags) { - mDvrrSupported = context.getResources().getBoolean( - com.android.internal.R.bool.config_supportsDvrr); mIsDisplayResolutionRangeVotingEnabled = displayManagerFlags .isDisplayResolutionRangeVotingEnabled(); mIsUserPreferredModeVoteEnabled = displayManagerFlags.isUserPreferredModeVoteEnabled(); @@ -219,23 +219,23 @@ public class DisplayModeDirector { displayManagerFlags.isRefreshRateVotingTelemetryEnabled()); mSupportedModesByDisplay = new SparseArray<>(); mDefaultModeByDisplay = new SparseArray<>(); + mVrrSupportedByDisplay = new SparseBooleanArray(); mAppRequestObserver = new AppRequestObserver(); mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); - mSettingsObserver = new SettingsObserver(context, handler, mDvrrSupported, - displayManagerFlags); - mBrightnessObserver = new BrightnessObserver(context, handler, injector, mDvrrSupported, + mSettingsObserver = new SettingsObserver(context, handler, displayManagerFlags); + mBrightnessObserver = new BrightnessObserver(context, handler, injector, displayManagerFlags); mDefaultDisplayDeviceConfig = null; mUdfpsObserver = new UdfpsObserver(); mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked, mVotesStatsReporter); - mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage); + mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage, injector); mSensorObserver = new ProximitySensorObserver(mVotesStorage, injector); mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage); mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(), mDeviceConfigDisplaySettings); - if (mDvrrSupported && displayManagerFlags.isRestrictDisplayModesEnabled()) { + if (displayManagerFlags.isRestrictDisplayModesEnabled()) { mSystemRequestObserver = new SystemRequestObserver(mVotesStorage); } else { mSystemRequestObserver = null; @@ -315,7 +315,8 @@ public class DisplayModeDirector { List<Display.Mode> availableModes = new ArrayList<>(); availableModes.add(defaultMode); VoteSummary primarySummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled, - mDvrrSupported, mLoggingEnabled, mSupportsFrameRateOverride); + mVrrSupportedByDisplay.get(displayId), + mLoggingEnabled, mSupportsFrameRateOverride); int lowestConsideredPriority = Vote.MIN_PRIORITY; int highestConsideredPriority = Vote.MAX_PRIORITY; @@ -355,7 +356,8 @@ public class DisplayModeDirector { } VoteSummary appRequestSummary = new VoteSummary(mIsDisplayResolutionRangeVotingEnabled, - mDvrrSupported, mLoggingEnabled, mSupportsFrameRateOverride); + mVrrSupportedByDisplay.get(displayId), + mLoggingEnabled, mSupportsFrameRateOverride); appRequestSummary.applyVotes(votes, Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, @@ -442,6 +444,15 @@ public class DisplayModeDirector { return mAppRequestObserver; } + private boolean isVrrSupportedByAnyDisplayLocked() { + for (int i = 0; i < mVrrSupportedByDisplay.size(); i++) { + if (mVrrSupportedByDisplay.valueAt(i)) { + return true; + } + } + return false; + } + /** * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges. */ @@ -539,7 +550,13 @@ public class DisplayModeDirector { */ public void requestDisplayModes(IBinder token, int displayId, int[] modeIds) { if (mSystemRequestObserver != null) { - mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds); + boolean vrrSupported; + synchronized (mLock) { + vrrSupported = mVrrSupportedByDisplay.get(displayId); + } + if (vrrSupported) { + mSystemRequestObserver.requestDisplayModes(token, displayId, modeIds); + } } } @@ -627,6 +644,11 @@ public class DisplayModeDirector { } @VisibleForTesting + void injectVrrByDisplay(SparseBooleanArray vrrByDisplay) { + mVrrSupportedByDisplay = vrrByDisplay; + } + + @VisibleForTesting void injectVotesByDisplay(SparseArray<SparseArray<Vote>> votesByDisplay) { mVotesStorage.injectVotesByDisplay(votesByDisplay); } @@ -906,11 +928,11 @@ public class DisplayModeDirector { private float mDefaultPeakRefreshRate; private float mDefaultRefreshRate; - SettingsObserver(@NonNull Context context, @NonNull Handler handler, boolean dvrrSupported, + SettingsObserver(@NonNull Context context, @NonNull Handler handler, DisplayManagerFlags flags) { super(handler); mContext = context; - mVsynLowPowerVoteEnabled = dvrrSupported && flags.isVsyncLowPowerVoteEnabled(); + mVsynLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled(); // We don't want to load from the DeviceConfig while constructing since this leads to // a spike in the latency of DisplayManagerService startup. This happens because // reading from the DeviceConfig is an intensive IO operation and having it in the @@ -1020,7 +1042,7 @@ public class DisplayModeDirector { boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0; final Vote vote; - if (inLowPowerMode && mVsynLowPowerVoteEnabled) { + if (inLowPowerMode && mVsynLowPowerVoteEnabled && isVrrSupportedByAnyDisplayLocked()) { vote = Vote.forSupportedRefreshRates(List.of( new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, /* vsyncRate= */ 240f), @@ -1190,8 +1212,8 @@ public class DisplayModeDirector { /** * Sets refresh rates from app request */ - public void setAppRequest(int displayId, int modeId, float requestedMinRefreshRateRange, - float requestedMaxRefreshRateRange) { + public void setAppRequest(int displayId, int modeId, + float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) { synchronized (mLock) { setAppRequestedModeLocked(displayId, modeId); setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange, @@ -1204,7 +1226,6 @@ public class DisplayModeDirector { if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) { return; } - final Vote baseModeRefreshRateVote; final Vote sizeVote; if (requestedMode != null) { @@ -1295,13 +1316,16 @@ public class DisplayModeDirector { private final Context mContext; private final Handler mHandler; private final VotesStorage mVotesStorage; + + private DisplayManagerInternal mDisplayManagerInternal; private int mExternalDisplayPeakWidth; private int mExternalDisplayPeakHeight; private int mExternalDisplayPeakRefreshRate; private final boolean mRefreshRateSynchronizationEnabled; private final Set<Integer> mExternalDisplaysConnected = new HashSet<>(); - DisplayObserver(Context context, Handler handler, VotesStorage votesStorage) { + DisplayObserver(Context context, Handler handler, VotesStorage votesStorage, + Injector injector) { mContext = context; mHandler = handler; mVotesStorage = votesStorage; @@ -1330,6 +1354,7 @@ public class DisplayModeDirector { } public void observe() { + mDisplayManagerInternal = mInjector.getDisplayManagerInternal(); mInjector.registerDisplayListener(this, mHandler); // Populate existing displays @@ -1342,17 +1367,21 @@ public class DisplayModeDirector { modes.put(displayId, info.supportedModes); defaultModes.put(displayId, info.getDefaultMode()); } + boolean vrrSupportedByDefaultDisplay = mDisplayManagerInternal + .isVrrSupportEnabled(Display.DEFAULT_DISPLAY); synchronized (mLock) { final int size = modes.size(); for (int i = 0; i < size; i++) { mSupportedModesByDisplay.put(modes.keyAt(i), modes.valueAt(i)); mDefaultModeByDisplay.put(defaultModes.keyAt(i), defaultModes.valueAt(i)); } + mVrrSupportedByDisplay.put(Display.DEFAULT_DISPLAY, vrrSupportedByDefaultDisplay); } } @Override public void onDisplayAdded(int displayId) { + updateVrrStatus(displayId); DisplayInfo displayInfo = getDisplayInfo(displayId); updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); @@ -1366,6 +1395,7 @@ public class DisplayModeDirector { synchronized (mLock) { mSupportedModesByDisplay.remove(displayId); mDefaultModeByDisplay.remove(displayId); + mVrrSupportedByDisplay.delete(displayId); mSettingsObserver.removeRefreshRateSetting(displayId); } updateLayoutLimitedFrameRate(displayId, null); @@ -1376,6 +1406,7 @@ public class DisplayModeDirector { @Override public void onDisplayChanged(int displayId) { + updateVrrStatus(displayId); DisplayInfo displayInfo = getDisplayInfo(displayId); updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); @@ -1505,6 +1536,13 @@ public class DisplayModeDirector { mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null); } + private void updateVrrStatus(int displayId) { + boolean isVrrSupported = mDisplayManagerInternal.isVrrSupportEnabled(displayId); + synchronized (mLock) { + mVrrSupportedByDisplay.put(displayId, isVrrSupported); + } + } + private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) { if (info == null) { return; @@ -1631,7 +1669,7 @@ public class DisplayModeDirector { private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE; BrightnessObserver(Context context, Handler handler, Injector injector, - boolean dvrrSupported , DisplayManagerFlags flags) { + DisplayManagerFlags flags) { mContext = context; mHandler = handler; mInjector = injector; @@ -1639,7 +1677,7 @@ public class DisplayModeDirector { /* attemptReadFromFeatureParams= */ false); mRefreshRateInHighZone = context.getResources().getInteger( R.integer.config_fixedRefreshRateInHighZone); - mVsyncLowLightBlockingVoteEnabled = dvrrSupported && flags.isVsyncLowLightVoteEnabled(); + mVsyncLowLightBlockingVoteEnabled = flags.isVsyncLowLightVoteEnabled(); } /** @@ -2225,7 +2263,8 @@ public class DisplayModeDirector { } } - if (mVsyncLowLightBlockingVoteEnabled) { + if (mVsyncLowLightBlockingVoteEnabled + && mVrrSupportedByDisplay.get(Display.DEFAULT_DISPLAY)) { refreshRateSwitchingVote = Vote.forSupportedRefreshRatesAndDisableSwitching( List.of( new SupportedRefreshRatesVote.RefreshRates( diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index a100fe06c407..3e23f972bd45 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -16,10 +16,12 @@ package com.android.server.inputmethod; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -410,7 +412,7 @@ final class InputMethodBindingController { Slog.v(TAG, "Removing window token: " + mCurToken + " for display: " + curTokenDisplayId); } - mWindowManagerInternal.removeWindowToken(mCurToken, false /* removeWindows */, + mWindowManagerInternal.removeWindowToken(mCurToken, true /* removeWindows */, false /* animateExit */, curTokenDisplayId); mCurToken = null; } @@ -452,9 +454,12 @@ final class InputMethodBindingController { intent.setComponent(component); intent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.input_method_binding_label); + var options = ActivityOptions.makeBasic() + .setPendingIntentCreatorBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_DENIED); intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), - PendingIntent.FLAG_IMMUTABLE)); + PendingIntent.FLAG_IMMUTABLE, options.toBundle())); return intent; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index c6a48ec9018e..1d07eee927b2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1336,77 +1336,78 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Context context, @Nullable ServiceThread serviceThreadForTesting, @Nullable InputMethodBindingController bindingControllerForTesting) { - mContext = context; - mRes = context.getResources(); - SecureSettingsWrapper.onStart(mContext); - // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading - // additional subtypes in switchUserOnHandlerLocked(). - final ServiceThread thread = - serviceThreadForTesting != null - ? serviceThreadForTesting - : new ServiceThread( - HANDLER_THREAD_NAME, - Process.THREAD_PRIORITY_FOREGROUND, - true /* allowIo */); - thread.start(); - mHandler = Handler.createAsync(thread.getLooper(), this); - SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler); - mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null - ? serviceThreadForTesting.getLooper() : Looper.getMainLooper()); - // Note: SettingsObserver doesn't register observers in its constructor. - mSettingsObserver = new SettingsObserver(mHandler); - mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); - mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); - mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); - mImePlatformCompatUtils = new ImePlatformCompatUtils(); - mInputMethodDeviceConfigs = new InputMethodDeviceConfigs(); - mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); - - mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime); - - mShowOngoingImeSwitcherForPhones = false; - - AdditionalSubtypeMapRepository.initialize(mHandler); - - final int userId = mActivityManagerInternal.getCurrentUserId(); - - // mSettings should be created before buildInputMethodListLocked - mSettings = InputMethodSettings.createEmptyMap(userId); - - mSwitchingController = - InputMethodSubtypeSwitchingController.createInstanceLocked(context, - mSettings.getMethodMap(), userId); - mHardwareKeyboardShortcutController = - new HardwareKeyboardShortcutController(mSettings.getMethodMap(), - mSettings.getUserId()); - mMenuController = new InputMethodMenuController(this); - mBindingController = - bindingControllerForTesting != null - ? bindingControllerForTesting - : new InputMethodBindingController(this); - mAutofillController = new AutofillSuggestionsController(this); - - mVisibilityStateComputer = new ImeVisibilityStateComputer(this); - mVisibilityApplier = new DefaultImeVisibilityApplier(this); - - mClientController = new ClientController(mPackageManagerInternal); synchronized (ImfLock.class) { + mContext = context; + mRes = context.getResources(); + SecureSettingsWrapper.onStart(mContext); + + // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading + // additional subtypes in switchUserOnHandlerLocked(). + final ServiceThread thread = + serviceThreadForTesting != null + ? serviceThreadForTesting + : new ServiceThread( + HANDLER_THREAD_NAME, + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); + thread.start(); + mHandler = Handler.createAsync(thread.getLooper(), this); + SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler); + mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null + ? serviceThreadForTesting.getLooper() : Looper.getMainLooper()); + // Note: SettingsObserver doesn't register observers in its constructor. + mSettingsObserver = new SettingsObserver(mHandler); + mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); + mImePlatformCompatUtils = new ImePlatformCompatUtils(); + mInputMethodDeviceConfigs = new InputMethodDeviceConfigs(); + mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); + + mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime); + + mShowOngoingImeSwitcherForPhones = false; + + AdditionalSubtypeMapRepository.initialize(mHandler); + + final int userId = mActivityManagerInternal.getCurrentUserId(); + + // mSettings should be created before buildInputMethodListLocked + mSettings = InputMethodSettings.createEmptyMap(userId); + + mSwitchingController = + InputMethodSubtypeSwitchingController.createInstanceLocked(context, + mSettings.getMethodMap(), userId); + mHardwareKeyboardShortcutController = + new HardwareKeyboardShortcutController(mSettings.getMethodMap(), + mSettings.getUserId()); + mMenuController = new InputMethodMenuController(this); + mBindingController = + bindingControllerForTesting != null + ? bindingControllerForTesting + : new InputMethodBindingController(this); + mAutofillController = new AutofillSuggestionsController(this); + + mVisibilityStateComputer = new ImeVisibilityStateComputer(this); + mVisibilityApplier = new DefaultImeVisibilityApplier(this); + + mClientController = new ClientController(mPackageManagerInternal); mClientController.addClientControllerCallback(c -> onClientRemoved(c)); mImeBindingState = ImeBindingState.newEmptyState(); - } - mPreventImeStartupUnlessTextEditor = mRes.getBoolean( - com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); - mNonPreemptibleInputMethods = mRes.getStringArray( - com.android.internal.R.array.config_nonPreemptibleInputMethods); - IntConsumer toolTypeConsumer = - Flags.useHandwritingListenerForTooltype() - ? toolType -> onUpdateEditorToolType(toolType) : null; - Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText(); - mHwController = new HandwritingModeController(mContext, thread.getLooper(), - new InkWindowInitializer(), toolTypeConsumer, discardDelegationTextRunnable); - registerDeviceListenerAndCheckStylusSupport(); + mPreventImeStartupUnlessTextEditor = mRes.getBoolean( + com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); + mNonPreemptibleInputMethods = mRes.getStringArray( + com.android.internal.R.array.config_nonPreemptibleInputMethods); + IntConsumer toolTypeConsumer = + Flags.useHandwritingListenerForTooltype() + ? toolType -> onUpdateEditorToolType(toolType) : null; + Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText(); + mHwController = new HandwritingModeController(mContext, thread.getLooper(), + new InkWindowInitializer(), toolTypeConsumer, discardDelegationTextRunnable); + registerDeviceListenerAndCheckStylusSupport(); + } } @GuardedBy("ImfLock.class") diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 62a947123ddf..93f68a831705 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -1362,12 +1362,18 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde @Override public IBinder getBinderForSetQueue() throws RemoteException { - return new ParcelableListBinder<QueueItem>((list) -> { - synchronized (mLock) { - mQueue = list; - } - mHandler.post(MessageHandler.MSG_UPDATE_QUEUE); - }); + return new ParcelableListBinder<QueueItem>( + (list) -> { + // Checking list items are instanceof QueueItem to validate against + // malicious apps calling it directly via reflection with non compilable + // items. See b/317048338 for more details + List<QueueItem> sanitizedQueue = + list.stream().filter(it -> it instanceof QueueItem).toList(); + synchronized (mLock) { + mQueue = sanitizedQueue; + } + mHandler.post(MessageHandler.MSG_UPDATE_QUEUE); + }); } @Override diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 956e10c79246..ebc1a2a45579 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3399,21 +3399,21 @@ public class NotificationManagerService extends SystemService { // ============================================================================ @Override - public void enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, + public boolean enqueueTextToast(String pkg, IBinder token, CharSequence text, int duration, boolean isUiContext, int displayId, @Nullable ITransientNotificationCallback textCallback) { - enqueueToast(pkg, token, text, /* callback= */ null, duration, isUiContext, displayId, - textCallback); + return enqueueToast(pkg, token, text, /* callback= */ null, duration, isUiContext, + displayId, textCallback); } @Override - public void enqueueToast(String pkg, IBinder token, ITransientNotification callback, + public boolean enqueueToast(String pkg, IBinder token, ITransientNotification callback, int duration, boolean isUiContext, int displayId) { - enqueueToast(pkg, token, /* text= */ null, callback, duration, isUiContext, displayId, - /* textCallback= */ null); + return enqueueToast(pkg, token, /* text= */ null, callback, duration, isUiContext, + displayId, /* textCallback= */ null); } - private void enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, + private boolean enqueueToast(String pkg, IBinder token, @Nullable CharSequence text, @Nullable ITransientNotification callback, int duration, boolean isUiContext, int displayId, @Nullable ITransientNotificationCallback textCallback) { if (DBG) { @@ -3425,7 +3425,7 @@ public class NotificationManagerService extends SystemService { || (text != null && callback != null) || token == null) { Slog.e(TAG, "Not enqueuing toast. pkg=" + pkg + " text=" + text + " callback=" + " token=" + token); - return; + return false; } final int callingUid = Binder.getCallingUid(); @@ -3451,7 +3451,7 @@ public class NotificationManagerService extends SystemService { boolean isAppRenderedToast = (callback != null); if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast, isSystemToast)) { - return; + return false; } synchronized (mToastQueue) { @@ -3477,7 +3477,7 @@ public class NotificationManagerService extends SystemService { if (count >= MAX_PACKAGE_TOASTS) { Slog.e(TAG, "Package has already queued " + count + " toasts. Not showing more. Package=" + pkg); - return; + return false; } } } @@ -3513,6 +3513,7 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(callingId); } } + return true; } @GuardedBy("mToastQueue") diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 4b8e4852aee7..6c93fe787816 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -32,6 +32,7 @@ import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_RRO; import static android.os.Trace.traceBegin; import static android.os.Trace.traceEnd; + import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException; import android.annotation.NonNull; @@ -279,7 +280,8 @@ public final class OverlayManagerService extends SystemService { HandlerThread packageMonitorThread = new HandlerThread(TAG); packageMonitorThread.start(); - mPackageMonitor.register(context, packageMonitorThread.getLooper(), true); + mPackageMonitor.register( + context, packageMonitorThread.getLooper(), UserHandle.ALL, true); final IntentFilter userFilter = new IntentFilter(); userFilter.addAction(ACTION_USER_ADDED); @@ -369,17 +371,17 @@ public final class OverlayManagerService extends SystemService { @Override public void onPackageAppearedWithExtras(String packageName, Bundle extras) { - handlePackageAdd(packageName, extras); + handlePackageAdd(packageName, extras, getChangingUserId()); } @Override public void onPackageChangedWithExtras(String packageName, Bundle extras) { - handlePackageChange(packageName, extras); + handlePackageChange(packageName, extras, getChangingUserId()); } @Override public void onPackageDisappearedWithExtras(String packageName, Bundle extras) { - handlePackageRemove(packageName, extras); + handlePackageRemove(packageName, extras, getChangingUserId()); } } @@ -393,54 +395,45 @@ public final class OverlayManagerService extends SystemService { return userIds; } - private void handlePackageAdd(String packageName, Bundle extras) { + private void handlePackageAdd(String packageName, Bundle extras, int userId) { final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); - final int uid = extras.getInt(Intent.EXTRA_UID, 0); - final int[] userIds = getUserIds(uid); if (replacing) { - onPackageReplaced(packageName, userIds); + onPackageReplaced(packageName, userId); } else { - onPackageAdded(packageName, userIds); + onPackageAdded(packageName, userId); } } - private void handlePackageChange(String packageName, Bundle extras) { - final int uid = extras.getInt(Intent.EXTRA_UID, 0); - final int[] userIds = getUserIds(uid); + private void handlePackageChange(String packageName, Bundle extras, int userId) { if (!ACTION_OVERLAY_CHANGED.equals(extras.getString(EXTRA_REASON))) { - onPackageChanged(packageName, userIds); + onPackageChanged(packageName, userId); } } - private void handlePackageRemove(String packageName, Bundle extras) { + private void handlePackageRemove(String packageName, Bundle extras, int userId) { final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); final boolean systemUpdateUninstall = extras.getBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false); - final int uid = extras.getInt(Intent.EXTRA_UID, 0); - final int[] userIds = getUserIds(uid); if (replacing) { - onPackageReplacing(packageName, systemUpdateUninstall, userIds); + onPackageReplacing(packageName, systemUpdateUninstall, userId); } else { - onPackageRemoved(packageName, userIds); + onPackageRemoved(packageName, userId); } } - private void onPackageAdded(@NonNull final String packageName, - @NonNull final int[] userIds) { + private void onPackageAdded(@NonNull final String packageName, final int userId) { try { traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName); - for (final int userId : userIds) { - synchronized (mLock) { - var packageState = mPackageManager.onPackageAdded(packageName, userId); - if (packageState != null && !mPackageManager.isInstantApp(packageName, - userId)) { - try { - updateTargetPackagesLocked( - mImpl.onPackageAdded(packageName, userId)); - } catch (OperationFailedException e) { - Slog.e(TAG, "onPackageAdded internal error", e); - } + synchronized (mLock) { + var packageState = mPackageManager.onPackageAdded(packageName, userId); + if (packageState != null && !mPackageManager.isInstantApp(packageName, + userId)) { + try { + updateTargetPackagesLocked( + mImpl.onPackageAdded(packageName, userId)); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageAdded internal error", e); } } } @@ -449,21 +442,18 @@ public final class OverlayManagerService extends SystemService { } } - private void onPackageChanged(@NonNull final String packageName, - @NonNull final int[] userIds) { + private void onPackageChanged(@NonNull final String packageName, final int userId) { try { traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName); - for (int userId : userIds) { - synchronized (mLock) { - var packageState = mPackageManager.onPackageUpdated(packageName, userId); - if (packageState != null && !mPackageManager.isInstantApp(packageName, - userId)) { - try { - updateTargetPackagesLocked( - mImpl.onPackageChanged(packageName, userId)); - } catch (OperationFailedException e) { - Slog.e(TAG, "onPackageChanged internal error", e); - } + synchronized (mLock) { + var packageState = mPackageManager.onPackageUpdated(packageName, userId); + if (packageState != null && !mPackageManager.isInstantApp(packageName, + userId)) { + try { + updateTargetPackagesLocked( + mImpl.onPackageChanged(packageName, userId)); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageChanged internal error", e); } } } @@ -473,20 +463,18 @@ public final class OverlayManagerService extends SystemService { } private void onPackageReplacing(@NonNull final String packageName, - boolean systemUpdateUninstall, @NonNull final int[] userIds) { + boolean systemUpdateUninstall, final int userId) { try { traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName); - for (int userId : userIds) { - synchronized (mLock) { - var packageState = mPackageManager.onPackageUpdated(packageName, userId); - if (packageState != null && !mPackageManager.isInstantApp(packageName, - userId)) { - try { - updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName, - systemUpdateUninstall, userId)); - } catch (OperationFailedException e) { - Slog.e(TAG, "onPackageReplacing internal error", e); - } + synchronized (mLock) { + var packageState = mPackageManager.onPackageUpdated(packageName, userId); + if (packageState != null && !mPackageManager.isInstantApp(packageName, + userId)) { + try { + updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName, + systemUpdateUninstall, userId)); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageReplacing internal error", e); } } } @@ -495,21 +483,18 @@ public final class OverlayManagerService extends SystemService { } } - private void onPackageReplaced(@NonNull final String packageName, - @NonNull final int[] userIds) { + private void onPackageReplaced(@NonNull final String packageName, final int userId) { try { traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName); - for (int userId : userIds) { - synchronized (mLock) { - var packageState = mPackageManager.onPackageUpdated(packageName, userId); - if (packageState != null && !mPackageManager.isInstantApp(packageName, - userId)) { - try { - updateTargetPackagesLocked( - mImpl.onPackageReplaced(packageName, userId)); - } catch (OperationFailedException e) { - Slog.e(TAG, "onPackageReplaced internal error", e); - } + synchronized (mLock) { + var packageState = mPackageManager.onPackageUpdated(packageName, userId); + if (packageState != null && !mPackageManager.isInstantApp(packageName, + userId)) { + try { + updateTargetPackagesLocked( + mImpl.onPackageReplaced(packageName, userId)); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageReplaced internal error", e); } } } @@ -518,15 +503,12 @@ public final class OverlayManagerService extends SystemService { } } - private void onPackageRemoved(@NonNull final String packageName, - @NonNull final int[] userIds) { + private void onPackageRemoved(@NonNull final String packageName, final int userId) { try { traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName); - for (int userId : userIds) { - synchronized (mLock) { - mPackageManager.onPackageRemoved(packageName, userId); - updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId)); - } + synchronized (mLock) { + mPackageManager.onPackageRemoved(packageName, userId); + updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId)); } } finally { traceEnd(TRACE_TAG_RRO); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 68cd3e463905..614828add52b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5753,17 +5753,22 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override public void setApplicationCategoryHint(String packageName, int categoryHint, String callerPackageName) { + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getCallingUserId(); final FunctionalUtils.ThrowingBiFunction<PackageStateMutator.InitialState, Computer, PackageStateMutator.Result> implementation = (initialState, computer) -> { - if (computer.getInstantAppPackageName(Binder.getCallingUid()) != null) { + if (computer.getInstantAppPackageName(callingUid) != null) { throw new SecurityException( "Instant applications don't have access to this method"); } - mInjector.getSystemService(AppOpsManager.class) - .checkPackage(Binder.getCallingUid(), callerPackageName); + final int callerPackageUid = computer.getPackageUid(callerPackageName, 0, userId); + if (callerPackageUid != callingUid) { + throw new SecurityException( + "Package " + callerPackageName + " does not belong to " + callingUid); + } PackageStateInternal packageState = computer.getPackageStateForInstalledAndFiltered( - packageName, Binder.getCallingUid(), UserHandle.getCallingUserId()); + packageName, callingUid, userId); if (packageState == null) { throw new IllegalArgumentException("Unknown target package " + packageName); } diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java index 1fe49c7d5834..7ef7ce7afb65 100644 --- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java +++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_APEX; + import android.apex.ApexInfo; import android.apex.ApexInfoList; import android.apex.ApexSessionInfo; @@ -399,7 +401,7 @@ final class PackageSessionVerifier { final ParsedPackage parsedPackage; try (PackageParser2 packageParser = mPackageParserSupplier.get()) { File apexFile = new File(apexInfo.modulePath); - parsedPackage = packageParser.parsePackage(apexFile, 0, false); + parsedPackage = packageParser.parsePackage(apexFile, PARSE_APEX, false); } catch (PackageParserException e) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 33b5a707d9df..1e7fdfec783f 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -306,7 +306,7 @@ public final class UserTypeFactory { R.color.black) .setDarkThemeBadgeColors( R.color.white) - .setDefaultRestrictions(getDefaultProfileRestrictions()) + .setDefaultRestrictions(getDefaultPrivateProfileRestrictions()) .setDefaultCrossProfileIntentFilters(getDefaultPrivateCrossProfileIntentFilter()) .setDefaultUserProperties(new UserProperties.Builder() .setStartWithParent(true) @@ -430,6 +430,13 @@ public final class UserTypeFactory { return restrictions; } + @VisibleForTesting + static Bundle getDefaultPrivateProfileRestrictions() { + final Bundle restrictions = getDefaultProfileRestrictions(); + restrictions.putBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING, true); + return restrictions; + } + private static Bundle getDefaultManagedProfileSecureSettings() { // Only add String values to the bundle, settings are written as Strings eventually final Bundle settings = new Bundle(); diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index 894226cf32c9..e1b4b88ed1df 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java @@ -34,11 +34,14 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.StringWriter; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TimeZone; /** * This class represents aggregated power stats for a variety of power components (CPU, WiFi, @@ -66,7 +69,7 @@ class AggregatedPowerStats { aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs(); mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()]; for (int i = 0; i < configs.size(); i++) { - mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(configs.get(i)); + mPowerComponentStats[i] = new PowerComponentAggregatedPowerStats(this, configs.get(i)); } } @@ -223,7 +226,7 @@ class AggregatedPowerStats { if (i == 0) { baseTime = clockUpdate.monotonicTime; sb.append("Start time: ") - .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime)) + .append(formatDateTime(clockUpdate.currentTime)) .append(" (") .append(baseTime) .append(") duration: ") @@ -235,8 +238,7 @@ class AggregatedPowerStats { TimeUtils.formatDuration( clockUpdate.monotonicTime - baseTime, sb, TimeUtils.HUNDRED_DAY_FIELD_LEN + 3); - sb.append(" ").append( - DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime)); + sb.append(" ").append(formatDateTime(clockUpdate.currentTime)); ipw.increaseIndent(); ipw.println(sb); ipw.decreaseIndent(); @@ -267,6 +269,12 @@ class AggregatedPowerStats { } } + private static String formatDateTime(long timeInMillis) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT")); + return format.format(new Date(timeInMillis)); + } + @Override public String toString() { StringWriter sw = new StringWriter(); 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 6fbbc0f072e8..5aad570ffd41 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -75,7 +75,7 @@ public class AggregatedPowerStatsConfig { private final int mPowerComponentId; private @TrackedState int[] mTrackedDeviceStates; private @TrackedState int[] mTrackedUidStates; - private AggregatedPowerStatsProcessor mProcessor = NO_OP_PROCESSOR; + private PowerStatsProcessor mProcessor = NO_OP_PROCESSOR; PowerComponent(int powerComponentId) { this.mPowerComponentId = powerComponentId; @@ -85,6 +85,9 @@ public class AggregatedPowerStatsConfig { * Configures which states should be tracked as separate dimensions for the entire device. */ public PowerComponent trackDeviceStates(@TrackedState int... states) { + if (mTrackedDeviceStates != null) { + throw new IllegalStateException("Component is already configured"); + } mTrackedDeviceStates = states; return this; } @@ -93,6 +96,9 @@ public class AggregatedPowerStatsConfig { * Configures which states should be tracked as separate dimensions on a per-UID basis. */ public PowerComponent trackUidStates(@TrackedState int... states) { + if (mTrackedUidStates != null) { + throw new IllegalStateException("Component is already configured"); + } mTrackedUidStates = states; return this; } @@ -102,7 +108,7 @@ public class AggregatedPowerStatsConfig { * before giving the aggregates stats to consumers. The processor can complete the * aggregation process, for example by computing estimated power usage. */ - public PowerComponent setProcessor(@NonNull AggregatedPowerStatsProcessor processor) { + public PowerComponent setProcessor(@NonNull PowerStatsProcessor processor) { mProcessor = processor; return this; } @@ -137,7 +143,7 @@ public class AggregatedPowerStatsConfig { } @NonNull - public AggregatedPowerStatsProcessor getProcessor() { + public PowerStatsProcessor getProcessor() { return mProcessor; } @@ -153,6 +159,7 @@ public class AggregatedPowerStatsConfig { } return false; } + } private final List<PowerComponent> mPowerComponents = new ArrayList<>(); @@ -168,23 +175,55 @@ public class AggregatedPowerStatsConfig { return builder; } + /** + * Creates a configuration for the specified power component, which is a subcomponent + * of a different power component. The tracked states will be the same as the parent + * component's. + */ + public PowerComponent trackPowerComponent(int powerComponentId, + int parentPowerComponentId) { + PowerComponent parent = null; + for (int i = 0; i < mPowerComponents.size(); i++) { + PowerComponent powerComponent = mPowerComponents.get(i); + if (powerComponent.getPowerComponentId() == parentPowerComponentId) { + parent = powerComponent; + break; + } + } + + if (parent == null) { + throw new IllegalArgumentException( + "Parent component " + parentPowerComponentId + " is not configured"); + } + + PowerComponent powerComponent = trackPowerComponent(powerComponentId); + powerComponent.mTrackedDeviceStates = parent.mTrackedDeviceStates; + powerComponent.mTrackedUidStates = parent.mTrackedUidStates; + return powerComponent; + } + public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() { return mPowerComponents; } - private static final AggregatedPowerStatsProcessor NO_OP_PROCESSOR = - new AggregatedPowerStatsProcessor() { + private static final PowerStatsProcessor NO_OP_PROCESSOR = + new PowerStatsProcessor() { @Override - public void finish(PowerComponentAggregatedPowerStats stats) { + void finish(PowerComponentAggregatedPowerStats stats) { } @Override - public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { return Arrays.toString(stats); } @Override - public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { + return descriptor.getStateLabel(key) + " " + Arrays.toString(stats); + } + + @Override + String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { return Arrays.toString(stats); } }; diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java index a8eda3ca6a47..cb10da9787df 100644 --- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java @@ -24,6 +24,7 @@ import android.hardware.power.stats.EnergyConsumer; import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.net.wifi.WifiManager; +import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.Bundle; import android.os.OutcomeReceiver; @@ -603,24 +604,31 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat } if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO) != 0) { - // We were asked to fetch Telephony data. - if (mTelephony != null) { - CompletableFuture<ModemActivityInfo> temp = new CompletableFuture<>(); - mTelephony.requestModemActivityInfo(Runnable::run, - new OutcomeReceiver<ModemActivityInfo, - TelephonyManager.ModemActivityInfoException>() { - @Override - public void onResult(ModemActivityInfo result) { - temp.complete(result); - } - - @Override - public void onError(TelephonyManager.ModemActivityInfoException e) { - Slog.w(TAG, "error reading modem stats:" + e); - temp.complete(null); - } - }); - modemFuture = temp; + @SuppressWarnings("GuardedBy") + PowerStatsCollector collector = mStats.getPowerStatsCollector( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO); + if (collector.isEnabled()) { + collector.schedule(); + } else { + // We were asked to fetch Telephony data. + if (mTelephony != null) { + CompletableFuture<ModemActivityInfo> temp = new CompletableFuture<>(); + mTelephony.requestModemActivityInfo(Runnable::run, + new OutcomeReceiver<ModemActivityInfo, + TelephonyManager.ModemActivityInfoException>() { + @Override + public void onResult(ModemActivityInfo result) { + temp.complete(result); + } + + @Override + public void onError(TelephonyManager.ModemActivityInfoException e) { + Slog.w(TAG, "error reading modem stats:" + e); + temp.complete(null); + } + }); + modemFuture = temp; + } } if (!railUpdated) { synchronized (mStats) { diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 3a84897839a1..fc2df578c6e3 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -22,6 +22,8 @@ import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE; import static android.os.BatteryStatsManager.NUM_WIFI_STATES; import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES; +import static com.android.server.power.stats.MobileRadioPowerStatsCollector.mapRadioAccessNetworkTypeToRadioAccessTechnology; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,6 +37,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.database.ContentObserver; import android.hardware.usb.UsbManager; import android.location.GnssSignalQuality; @@ -71,6 +74,7 @@ import android.os.connectivity.CellularBatteryStats; import android.os.connectivity.GpsBatteryStats; import android.os.connectivity.WifiActivityEnergyInfo; import android.os.connectivity.WifiBatteryStats; +import android.power.PowerStatsInternal; import android.provider.Settings; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation.NetworkType; @@ -99,6 +103,7 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseDoubleArray; import android.util.SparseIntArray; import android.util.SparseLongArray; @@ -137,6 +142,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.LocalServices; import com.android.server.power.optimization.Flags; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; @@ -166,8 +172,12 @@ import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.IntSupplier; +import java.util.function.LongSupplier; +import java.util.function.Supplier; /** * All information we are collecting about things that can happen that impact @@ -280,8 +290,9 @@ public class BatteryStatsImpl extends BatteryStats { private KernelMemoryBandwidthStats mKernelMemoryBandwidthStats; private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>(); private int[] mCpuPowerBracketMap; - private final CpuPowerStatsCollector mCpuPowerStatsCollector; - private boolean mPowerStatsCollectorEnabled; + private CpuPowerStatsCollector mCpuPowerStatsCollector; + private MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector; + private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray(); public LongSparseArray<SamplingTimer> getKernelMemoryStats() { return mKernelMemoryStats; @@ -433,9 +444,11 @@ public class BatteryStatsImpl extends BatteryStats { public static class BatteryStatsConfig { static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0; static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1; + static final long DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD = + TimeUnit.HOURS.toMillis(1); private final int mFlags; - private final long mPowerStatsThrottlePeriodCpu; + private SparseLongArray mPowerStatsThrottlePeriods; private BatteryStatsConfig(Builder builder) { int flags = 0; @@ -446,7 +459,7 @@ public class BatteryStatsImpl extends BatteryStats { flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG; } mFlags = flags; - mPowerStatsThrottlePeriodCpu = builder.mPowerStatsThrottlePeriodCpu; + mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods; } /** @@ -467,8 +480,9 @@ public class BatteryStatsImpl extends BatteryStats { == RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG; } - long getPowerStatsThrottlePeriodCpu() { - return mPowerStatsThrottlePeriodCpu; + long getPowerStatsThrottlePeriod(@BatteryConsumer.PowerComponent int powerComponent) { + return mPowerStatsThrottlePeriods.get(powerComponent, + DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD); } /** @@ -477,12 +491,16 @@ public class BatteryStatsImpl extends BatteryStats { public static class Builder { private boolean mResetOnUnplugHighBatteryLevel; private boolean mResetOnUnplugAfterSignificantCharge; - private long mPowerStatsThrottlePeriodCpu; + private SparseLongArray mPowerStatsThrottlePeriods; public Builder() { mResetOnUnplugHighBatteryLevel = true; mResetOnUnplugAfterSignificantCharge = true; - mPowerStatsThrottlePeriodCpu = 60000; + mPowerStatsThrottlePeriods = new SparseLongArray(); + setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU, + TimeUnit.MINUTES.toMillis(1)); + setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, + TimeUnit.HOURS.toMillis(1)); } /** @@ -512,10 +530,11 @@ public class BatteryStatsImpl extends BatteryStats { /** * Sets the minimum amount of time (in millis) to wait between passes - * of CPU power stats collection. + * of power stats collection for the specified power component. */ - public Builder setPowerStatsThrottlePeriodCpu(long periodMs) { - mPowerStatsThrottlePeriodCpu = periodMs; + public Builder setPowerStatsThrottlePeriodMillis( + @BatteryConsumer.PowerComponent int powerComponent, long periodMs) { + mPowerStatsThrottlePeriods.put(powerComponent, periodMs); return this; } } @@ -597,7 +616,7 @@ public class BatteryStatsImpl extends BatteryStats { @SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter @VisibleForTesting public void updateProcStateCpuTimesLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { - if (mPowerStatsCollectorEnabled) { + if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) { return; } @@ -653,7 +672,7 @@ public class BatteryStatsImpl extends BatteryStats { */ @SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter public void updateCpuTimesForAllUids() { - if (mPowerStatsCollectorEnabled && mCpuPowerStatsCollector != null) { + if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) { mCpuPowerStatsCollector.schedule(); return; } @@ -713,7 +732,8 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") private void ensureKernelSingleUidTimeReaderLocked() { - if (mPowerStatsCollectorEnabled || mKernelSingleUidTimeReader != null) { + if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU) + || mKernelSingleUidTimeReader != null) { return; } @@ -830,8 +850,6 @@ public class BatteryStatsImpl extends BatteryStats { private final HistoryEventTracker mActiveEvents = new HistoryEventTracker(); private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator = new HistoryStepDetailsCalculatorImpl(); - private final PowerStats.DescriptorRegistry mPowerStatsDescriptorRegistry = - new PowerStats.DescriptorRegistry(); private boolean mHaveBatteryLevel = false; private boolean mBatteryPluggedIn; @@ -1838,19 +1856,28 @@ public class BatteryStatsImpl extends BatteryStats { FrameworkStatsLog.write( FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin); } + + /** + * Records a statsd event when the batterystats config file is written to disk. + */ + public void writeCommitSysConfigFile(String fileName, long durationMs) { + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(fileName, + durationMs); + } } private final FrameworkStatsLogger mFrameworkStatsLogger; @VisibleForTesting - public BatteryStatsImpl(Clock clock, File historyDirectory, @NonNull Handler handler, + public BatteryStatsImpl(@NonNull BatteryStatsConfig config, Clock clock, File historyDirectory, + @NonNull Handler handler, @NonNull PowerStatsUidResolver powerStatsUidResolver, @NonNull FrameworkStatsLogger frameworkStatsLogger, @NonNull BatteryStatsHistory.TraceDelegate traceDelegate, @NonNull BatteryStatsHistory.EventLogger eventLogger) { + mBatteryStatsConfig = config; mClock = clock; initKernelStatsReaders(); - mBatteryStatsConfig = new BatteryStatsConfig.Builder().build(); mHandler = handler; mPowerStatsUidResolver = powerStatsUidResolver; mFrameworkStatsLogger = frameworkStatsLogger; @@ -1873,7 +1900,7 @@ public class BatteryStatsImpl extends BatteryStats { mPlatformIdleStateCallback = null; mEnergyConsumerRetriever = null; mUserInfoProvider = null; - mCpuPowerStatsCollector = null; + initPowerStatsCollectors(); } private void initKernelStatsReaders() { @@ -1893,6 +1920,105 @@ public class BatteryStatsImpl extends BatteryStats { mTmpRailStats = new RailStats(); } + private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector, + MobileRadioPowerStatsCollector.Injector { + private PackageManager mPackageManager; + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + private NetworkStatsManager mNetworkStatsManager; + private TelephonyManager mTelephonyManager; + + void setContext(Context context) { + mPackageManager = context.getPackageManager(); + mConsumedEnergyRetriever = new PowerStatsCollector.ConsumedEnergyRetrieverImpl( + LocalServices.getService(PowerStatsInternal.class)); + mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class); + mTelephonyManager = context.getSystemService(TelephonyManager.class); + } + + @Override + public Handler getHandler() { + return mHandler; + } + + @Override + public Clock getClock() { + return mClock; + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public CpuScalingPolicies getCpuScalingPolicies() { + return mCpuScalingPolicies; + } + + @Override + public PowerProfile getPowerProfile() { + return mPowerProfile; + } + + @Override + public CpuPowerStatsCollector.KernelCpuStatsReader getKernelCpuStatsReader() { + return new CpuPowerStatsCollector.KernelCpuStatsReader(); + } + + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> mBatteryVoltageMv; + } + + @Override + public Supplier<NetworkStats> getMobileNetworkStatsSupplier() { + return () -> readMobileNetworkStatsLocked(mNetworkStatsManager); + } + + @Override + public TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + + @Override + public LongSupplier getCallDurationSupplier() { + return () -> mPhoneOnTimer.getTotalTimeLocked(mClock.elapsedRealtime() * 1000, + STATS_SINCE_CHARGED); + } + + @Override + public LongSupplier getPhoneSignalScanDurationSupplier() { + return () -> mPhoneSignalScanningTimer.getTotalTimeLocked( + mClock.elapsedRealtime() * 1000, STATS_SINCE_CHARGED); + } + } + + private final PowerStatsCollectorInjector mPowerStatsCollectorInjector = + new PowerStatsCollectorInjector(); + + @SuppressWarnings("GuardedBy") // Accessed from constructor only + private void initPowerStatsCollectors() { + mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector, + mBatteryStatsConfig.getPowerStatsThrottlePeriod( + BatteryConsumer.POWER_COMPONENT_CPU)); + mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); + + mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector( + mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)); + mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats); + } + /** * TimeBase observer. */ @@ -5738,16 +5864,19 @@ public class BatteryStatsImpl extends BatteryStats { mMobileRadioActiveTimer.stopRunningLocked(realElapsedRealtimeMs); mMobileRadioActivePerAppTimer.stopRunningLocked(realElapsedRealtimeMs); - if (mLastModemActivityInfo != null) { - if (elapsedRealtimeMs < mLastModemActivityInfo.getTimestampMillis() + if (mMobileRadioPowerStatsCollector.isEnabled()) { + mMobileRadioPowerStatsCollector.schedule(); + } else { + // Check if modem Activity info has been collected recently, don't bother + // triggering another update. + if (mLastModemActivityInfo == null + || elapsedRealtimeMs >= mLastModemActivityInfo.getTimestampMillis() + MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS) { - // Modem Activity info has been collected recently, don't bother - // triggering another update. - return false; + mExternalSync.scheduleSync("modem-data", + BatteryExternalStatsWorker.UPDATE_RADIO); + return true; } } - // Tell the caller to collect radio network/power stats. - return true; } } return false; @@ -5915,6 +6044,7 @@ public class BatteryStatsImpl extends BatteryStats { mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs); if (mConstants.PHONE_ON_EXTERNAL_STATS_COLLECTION) { scheduleSyncExternalStatsLocked("phone-on", ExternalStatsSync.UPDATE_RADIO); + mMobileRadioPowerStatsCollector.schedule(); } } } @@ -5927,6 +6057,7 @@ public class BatteryStatsImpl extends BatteryStats { mPhoneOn = false; mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs); scheduleSyncExternalStatsLocked("phone-off", ExternalStatsSync.UPDATE_RADIO); + mMobileRadioPowerStatsCollector.schedule(); } } @@ -6269,27 +6400,6 @@ public class BatteryStatsImpl extends BatteryStats { } } - @RadioAccessTechnology - private static int mapRadioAccessNetworkTypeToRadioAccessTechnology( - @AccessNetworkConstants.RadioAccessNetworkType int dataType) { - switch (dataType) { - case AccessNetworkConstants.AccessNetworkType.NGRAN: - return RADIO_ACCESS_TECHNOLOGY_NR; - case AccessNetworkConstants.AccessNetworkType.EUTRAN: - return RADIO_ACCESS_TECHNOLOGY_LTE; - case AccessNetworkConstants.AccessNetworkType.UNKNOWN: //fallthrough - case AccessNetworkConstants.AccessNetworkType.GERAN: //fallthrough - case AccessNetworkConstants.AccessNetworkType.UTRAN: //fallthrough - case AccessNetworkConstants.AccessNetworkType.CDMA2000: //fallthrough - case AccessNetworkConstants.AccessNetworkType.IWLAN: - return RADIO_ACCESS_TECHNOLOGY_OTHER; - default: - Slog.w(TAG, - "Unhandled RadioAccessNetworkType (" + dataType + "), mapping to OTHER"); - return RADIO_ACCESS_TECHNOLOGY_OTHER; - } - } - @GuardedBy("this") public void noteWifiOnLocked(long elapsedRealtimeMs, long uptimeMs) { if (!mWifiOn) { @@ -8311,7 +8421,7 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("mBsi") private void ensureMultiStateCounters(long timestampMs) { - if (mBsi.mPowerStatsCollectorEnabled) { + if (mBsi.mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) { throw new IllegalStateException("Multi-state counters used in streamlined mode"); } @@ -10612,7 +10722,8 @@ public class BatteryStatsImpl extends BatteryStats { mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs); } - if (!mBsi.mPowerStatsCollectorEnabled && mBsi.trackPerProcStateCpuTimes()) { + if (!mBsi.mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU) + && mBsi.trackPerProcStateCpuTimes()) { mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs, uptimeMs); LongArrayMultiStateCounter onBatteryCounter = @@ -10634,7 +10745,8 @@ public class BatteryStatsImpl extends BatteryStats { final int batteryConsumerProcessState = mapUidProcessStateToBatteryConsumerProcessState(uidRunningState); - if (mBsi.mSystemReady && mBsi.mPowerStatsCollectorEnabled) { + if (mBsi.mSystemReady && mBsi.mPowerStatsCollectorEnabled.get( + BatteryConsumer.POWER_COMPONENT_CPU)) { mBsi.mHistory.recordProcessStateChange(elapsedRealtimeMs, uptimeMs, mUid, batteryConsumerProcessState); } @@ -11016,11 +11128,7 @@ public class BatteryStatsImpl extends BatteryStats { mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock); } - mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile, - mPowerStatsUidResolver, () -> mBatteryVoltageMv, mHandler, - mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu()); - mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); - + initPowerStatsCollectors(); mStartCount++; initTimersAndCounters(); mOnBattery = mOnBatteryInternal = false; @@ -11296,8 +11404,7 @@ public class BatteryStatsImpl extends BatteryStats { memStream.writeTo(stream); stream.flush(); mDailyFile.finishWrite(stream); - com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( - "batterystats-daily", + mFrameworkStatsLogger.writeCommitSysConfigFile("batterystats-daily", initialTimeMs + SystemClock.uptimeMillis() - startTimeMs2); } catch (IOException e) { Slog.w("BatteryStats", @@ -11809,7 +11916,7 @@ public class BatteryStatsImpl extends BatteryStats { // Store the empty state to disk to ensure consistency writeSyncLocked(); - if (mPowerStatsCollectorEnabled) { + if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) { schedulePowerStatsSampleCollection(); } @@ -11953,7 +12060,7 @@ public class BatteryStatsImpl extends BatteryStats { return networkStatsManager.getWifiUidStats(); } - private static class NetworkStatsDelta { + static class NetworkStatsDelta { int mUid; int mSet; long mRxBytes; @@ -11985,9 +12092,16 @@ public class BatteryStatsImpl extends BatteryStats { public long getTxPackets() { return mTxPackets; } + + @Override + public String toString() { + return "NetworkStatsDelta{mUid=" + mUid + ", mSet=" + mSet + ", mRxBytes=" + mRxBytes + + ", mRxPackets=" + mRxPackets + ", mTxBytes=" + mTxBytes + ", mTxPackets=" + + mTxPackets + '}'; + } } - private List<NetworkStatsDelta> computeDelta(NetworkStats currentStats, + static List<NetworkStatsDelta> computeDelta(NetworkStats currentStats, NetworkStats lastStats) { List<NetworkStatsDelta> deltaList = new ArrayList<>(); for (NetworkStats.Entry entry : currentStats) { @@ -12418,13 +12532,11 @@ public class BatteryStatsImpl extends BatteryStats { addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs); // Grab a separate lock to acquire the network stats, which may do I/O. - NetworkStats delta = null; + List<NetworkStatsDelta> delta = null; synchronized (mModemNetworkLock) { final NetworkStats latestStats = readMobileNetworkStatsLocked(networkStatsManager); if (latestStats != null) { - delta = latestStats.subtract(mLastModemNetworkStats != null - ? mLastModemNetworkStats - : new NetworkStats(0, -1)); + delta = computeDelta(latestStats, mLastModemNetworkStats); mLastModemNetworkStats = latestStats; } } @@ -12527,7 +12639,7 @@ public class BatteryStatsImpl extends BatteryStats { long totalRxPackets = 0; long totalTxPackets = 0; if (delta != null) { - for (NetworkStats.Entry entry : delta) { + for (NetworkStatsDelta entry : delta) { if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) { continue; } @@ -12568,7 +12680,7 @@ public class BatteryStatsImpl extends BatteryStats { // Now distribute proportional blame to the apps that did networking. long totalPackets = totalRxPackets + totalTxPackets; if (totalPackets > 0) { - for (NetworkStats.Entry entry : delta) { + for (NetworkStatsDelta entry : delta) { if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) { continue; } @@ -14408,17 +14520,41 @@ public class BatteryStatsImpl extends BatteryStats { /** * Notifies BatteryStatsImpl that the system server is ready. */ - public void onSystemReady() { + public void onSystemReady(Context context) { if (mCpuUidFreqTimeReader != null) { mCpuUidFreqTimeReader.onSystemReady(); } - if (mCpuPowerStatsCollector != null) { - mCpuPowerStatsCollector.setEnabled(mPowerStatsCollectorEnabled); - } + + mPowerStatsCollectorInjector.setContext(context); + + mCpuPowerStatsCollector.setEnabled( + mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)); + mCpuPowerStatsCollector.schedule(); + + mMobileRadioPowerStatsCollector.setEnabled( + mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)); + mMobileRadioPowerStatsCollector.schedule(); + mSystemReady = true; } /** + * Returns a PowerStatsCollector for the specified power component or null if unavailable. + */ + @Nullable + PowerStatsCollector getPowerStatsCollector( + @BatteryConsumer.PowerComponent int powerComponent) { + switch (powerComponent) { + case BatteryConsumer.POWER_COMPONENT_CPU: + return mCpuPowerStatsCollector; + case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO: + return mMobileRadioPowerStatsCollector; + } + return null; + } + + + /** * Force recording of all history events regardless of the "charging" state. */ @VisibleForTesting @@ -14561,9 +14697,10 @@ public class BatteryStatsImpl extends BatteryStats { stream.write(parcel.marshall()); stream.flush(); mCheckinFile.finishWrite(stream); - com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( - "batterystats-checkin", initialTimeMs - + SystemClock.uptimeMillis() - startTimeMs2); + mFrameworkStatsLogger.writeCommitSysConfigFile( + "batterystats-checkin", + initialTimeMs + SystemClock.uptimeMillis() + - startTimeMs2); } catch (IOException e) { Slog.w("BatteryStats", "Error writing checkin battery statistics", e); @@ -15437,9 +15574,10 @@ public class BatteryStatsImpl extends BatteryStats { /** * Enables or disables the PowerStatsCollector mode. */ - public void setPowerStatsCollectorEnabled(boolean enabled) { + public void setPowerStatsCollectorEnabled(@BatteryConsumer.PowerComponent int powerComponent, + boolean enabled) { synchronized (this) { - mPowerStatsCollectorEnabled = enabled; + mPowerStatsCollectorEnabled.put(powerComponent, enabled); } } @@ -15944,10 +16082,8 @@ public class BatteryStatsImpl extends BatteryStats { * Callers will need to wait for the collection to complete on the handler thread. */ public void schedulePowerStatsSampleCollection() { - if (mCpuPowerStatsCollector == null) { - return; - } mCpuPowerStatsCollector.forceSchedule(); + mMobileRadioPowerStatsCollector.forceSchedule(); } /** @@ -15965,6 +16101,7 @@ public class BatteryStatsImpl extends BatteryStats { */ public void dumpStatsSample(PrintWriter pw) { mCpuPowerStatsCollector.collectAndDump(pw); + mMobileRadioPowerStatsCollector.collectAndDump(pw); } private final Runnable mWriteAsyncRunnable = () -> { @@ -16036,7 +16173,7 @@ public class BatteryStatsImpl extends BatteryStats { + " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs) + " bytes:" + p.dataSize()); } - com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + mFrameworkStatsLogger.writeCommitSysConfigFile( "batterystats", SystemClock.uptimeMillis() - startTimeMs); } catch (IOException e) { Slog.w(TAG, "Error writing battery statistics", e); @@ -17262,10 +17399,8 @@ public class BatteryStatsImpl extends BatteryStats { pw.println(); dumpConstantsLocked(pw); - if (mCpuPowerStatsCollector != null) { - pw.println(); - mCpuPowerStatsCollector.dumpCpuPowerBracketsLocked(pw); - } + pw.println(); + mCpuPowerStatsCollector.dumpCpuPowerBracketsLocked(pw); pw.println(); dumpEnergyConsumerStatsLocked(pw); diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 30b80ae781ff..97f09865beeb 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -27,6 +27,7 @@ import android.os.UidBatteryConsumer; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.os.Clock; import com.android.internal.os.CpuScalingPolicies; @@ -43,7 +44,7 @@ import java.util.List; public class BatteryUsageStatsProvider { private static final String TAG = "BatteryUsageStatsProv"; private final Context mContext; - private boolean mPowerStatsExporterEnabled; + private final SparseBooleanArray mPowerStatsExporterEnabled = new SparseBooleanArray(); private final PowerStatsExporter mPowerStatsExporter; private final PowerStatsStore mPowerStatsStore; private final PowerProfile mPowerProfile; @@ -71,14 +72,20 @@ public class BatteryUsageStatsProvider { // Power calculators are applied in the order of registration mPowerCalculators.add(new BatteryChargeCalculator()); - if (!mPowerStatsExporterEnabled) { + if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) { mPowerCalculators.add( new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile)); } mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile)); mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile)); if (!BatteryStats.checkWifiOnly(mContext)) { - mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile)); + if (!mPowerStatsExporterEnabled.get( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) { + mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile)); + } + if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_PHONE)) { + mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile)); + } } mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile)); mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile)); @@ -89,7 +96,6 @@ public class BatteryUsageStatsProvider { mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile)); mPowerCalculators.add(new AudioPowerCalculator(mPowerProfile)); mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile)); mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile)); mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile)); @@ -228,7 +234,7 @@ public class BatteryUsageStatsProvider { } } - if (mPowerStatsExporterEnabled) { + if (mPowerStatsExporterEnabled.indexOfValue(true) >= 0) { mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder, monotonicStartTime, monotonicEndTime); } @@ -393,7 +399,10 @@ public class BatteryUsageStatsProvider { return builder.build(); } - public void setPowerStatsExporterEnabled(boolean enabled) { - mPowerStatsExporterEnabled = enabled; + /** + * Specify whether PowerStats based attribution is supported for the specified component. + */ + public void setPowerStatsExporterEnabled(int powerComponentId, boolean enabled) { + mPowerStatsExporterEnabled.put(powerComponentId, enabled); } } diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java index 1af127175f80..b1b2cc91d379 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java @@ -16,14 +16,11 @@ package com.android.server.power.stats; -import android.hardware.power.stats.EnergyConsumer; -import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; import android.os.Handler; import android.os.PersistableBundle; import android.os.Process; -import android.power.PowerStatsInternal; import android.util.Slog; import android.util.SparseArray; @@ -34,20 +31,11 @@ import com.android.internal.os.Clock; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerStats; -import com.android.server.LocalServices; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; -import java.util.List; import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.IntSupplier; -import java.util.function.Supplier; /** * Collects snapshots of power-related system statistics. @@ -63,213 +51,54 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2; private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000; + interface Injector { + Handler getHandler(); + Clock getClock(); + PowerStatsUidResolver getUidResolver(); + CpuScalingPolicies getCpuScalingPolicies(); + PowerProfile getPowerProfile(); + KernelCpuStatsReader getKernelCpuStatsReader(); + ConsumedEnergyRetriever getConsumedEnergyRetriever(); + IntSupplier getVoltageSupplier(); + + default int getDefaultCpuPowerBrackets() { + return DEFAULT_CPU_POWER_BRACKETS; + } + + default int getDefaultCpuPowerBracketsPerEnergyConsumer() { + return DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER; + } + } + + private final Injector mInjector; + private boolean mIsInitialized; - private final CpuScalingPolicies mCpuScalingPolicies; - private final PowerProfile mPowerProfile; - private final KernelCpuStatsReader mKernelCpuStatsReader; - private final PowerStatsUidResolver mUidResolver; - private final Supplier<PowerStatsInternal> mPowerStatsSupplier; - private final IntSupplier mVoltageSupplier; - private final int mDefaultCpuPowerBrackets; - private final int mDefaultCpuPowerBracketsPerEnergyConsumer; + private CpuScalingPolicies mCpuScalingPolicies; + private PowerProfile mPowerProfile; + private KernelCpuStatsReader mKernelCpuStatsReader; + private PowerStatsUidResolver mUidResolver; + private ConsumedEnergyRetriever mConsumedEnergyRetriever; + private IntSupplier mVoltageSupplier; + private int mDefaultCpuPowerBrackets; + private int mDefaultCpuPowerBracketsPerEnergyConsumer; private long[] mCpuTimeByScalingStep; private long[] mTempCpuTimeByScalingStep; private long[] mTempUidStats; private final SparseArray<UidStats> mUidStats = new SparseArray<>(); private boolean mIsPerUidTimeInStateSupported; - private PowerStatsInternal mPowerStatsInternal; private int[] mCpuEnergyConsumerIds = new int[0]; private PowerStats.Descriptor mPowerStatsDescriptor; // Reusable instance private PowerStats mCpuPowerStats; - private CpuStatsArrayLayout mLayout; + private CpuPowerStatsLayout mLayout; private long mLastUpdateTimestampNanos; private long mLastUpdateUptimeMillis; private int mLastVoltageMv; private long[] mLastConsumedEnergyUws; - /** - * Captures the positions and lengths of sections of the stats array, such as time-in-state, - * power usage estimates etc. - */ - public static class CpuStatsArrayLayout extends StatsArrayLayout { - private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt"; - private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc"; - private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc"; - private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc"; - private static final String EXTRA_UID_BRACKETS_POSITION = "ub"; - private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us"; - - private int mDeviceCpuTimeByScalingStepPosition; - private int mDeviceCpuTimeByScalingStepCount; - private int mDeviceCpuTimeByClusterPosition; - private int mDeviceCpuTimeByClusterCount; - - private int mUidPowerBracketsPosition; - private int mUidPowerBracketCount; - - private int[] mScalingStepToPowerBracketMap; - - /** - * Declare that the stats array has a section capturing CPU time per scaling step - */ - public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) { - mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount); - mDeviceCpuTimeByScalingStepCount = scalingStepCount; - } - - public int getCpuScalingStepCount() { - return mDeviceCpuTimeByScalingStepCount; - } - - /** - * Saves the time duration in the <code>stats</code> element - * corresponding to the CPU scaling <code>state</code>. - */ - public void setTimeByScalingStep(long[] stats, int step, long value) { - stats[mDeviceCpuTimeByScalingStepPosition + step] = value; - } - - /** - * Extracts the time duration from the <code>stats</code> element - * corresponding to the CPU scaling <code>step</code>. - */ - public long getTimeByScalingStep(long[] stats, int step) { - return stats[mDeviceCpuTimeByScalingStepPosition + step]; - } - - /** - * Declare that the stats array has a section capturing CPU time in each cluster - */ - public void addDeviceSectionCpuTimeByCluster(int clusterCount) { - mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount); - mDeviceCpuTimeByClusterCount = clusterCount; - } - - public int getCpuClusterCount() { - return mDeviceCpuTimeByClusterCount; - } - - /** - * Saves the time duration in the <code>stats</code> element - * corresponding to the CPU <code>cluster</code>. - */ - public void setTimeByCluster(long[] stats, int cluster, long value) { - stats[mDeviceCpuTimeByClusterPosition + cluster] = value; - } - - /** - * Extracts the time duration from the <code>stats</code> element - * corresponding to the CPU <code>cluster</code>. - */ - public long getTimeByCluster(long[] stats, int cluster) { - return stats[mDeviceCpuTimeByClusterPosition + cluster]; - } - - /** - * Declare that the UID stats array has a section capturing CPU time per power bracket. - */ - public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) { - mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap; - updatePowerBracketCount(); - mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount); - } - - private void updatePowerBracketCount() { - mUidPowerBracketCount = 1; - for (int bracket : mScalingStepToPowerBracketMap) { - if (bracket >= mUidPowerBracketCount) { - mUidPowerBracketCount = bracket + 1; - } - } - } - - public int[] getScalingStepToPowerBracketMap() { - return mScalingStepToPowerBracketMap; - } - - public int getCpuPowerBracketCount() { - return mUidPowerBracketCount; - } - - /** - * Saves time in <code>bracket</code> in the corresponding section of <code>stats</code>. - */ - public void setUidTimeByPowerBracket(long[] stats, int bracket, long value) { - stats[mUidPowerBracketsPosition + bracket] = value; - } - - /** - * Extracts the time in <code>bracket</code> from a UID stats array. - */ - public long getUidTimeByPowerBracket(long[] stats, int bracket) { - return stats[mUidPowerBracketsPosition + bracket]; - } - - /** - * Copies the elements of the stats array layout into <code>extras</code> - */ - public void toExtras(PersistableBundle extras) { - super.toExtras(extras); - extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION, - mDeviceCpuTimeByScalingStepPosition); - extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT, - mDeviceCpuTimeByScalingStepCount); - extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION, - mDeviceCpuTimeByClusterPosition); - extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT, - mDeviceCpuTimeByClusterCount); - extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition); - putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET, - mScalingStepToPowerBracketMap); - } - - /** - * Retrieves elements of the stats array layout from <code>extras</code> - */ - public void fromExtras(PersistableBundle extras) { - super.fromExtras(extras); - mDeviceCpuTimeByScalingStepPosition = - extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION); - mDeviceCpuTimeByScalingStepCount = - extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT); - mDeviceCpuTimeByClusterPosition = - extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION); - mDeviceCpuTimeByClusterCount = - extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT); - mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION); - mScalingStepToPowerBracketMap = - getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET); - if (mScalingStepToPowerBracketMap == null) { - mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount]; - } - updatePowerBracketCount(); - } - } - - public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile, - PowerStatsUidResolver uidResolver, IntSupplier voltageSupplier, Handler handler, - long throttlePeriodMs) { - this(cpuScalingPolicies, powerProfile, handler, new KernelCpuStatsReader(), uidResolver, - () -> LocalServices.getService(PowerStatsInternal.class), voltageSupplier, - throttlePeriodMs, Clock.SYSTEM_CLOCK, DEFAULT_CPU_POWER_BRACKETS, - DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER); - } - - public CpuPowerStatsCollector(CpuScalingPolicies cpuScalingPolicies, PowerProfile powerProfile, - Handler handler, KernelCpuStatsReader kernelCpuStatsReader, - PowerStatsUidResolver uidResolver, Supplier<PowerStatsInternal> powerStatsSupplier, - IntSupplier voltageSupplier, long throttlePeriodMs, Clock clock, - int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { - super(handler, throttlePeriodMs, clock); - mCpuScalingPolicies = cpuScalingPolicies; - mPowerProfile = powerProfile; - mKernelCpuStatsReader = kernelCpuStatsReader; - mUidResolver = uidResolver; - mPowerStatsSupplier = powerStatsSupplier; - mVoltageSupplier = voltageSupplier; - mDefaultCpuPowerBrackets = defaultCpuPowerBrackets; - mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer; + public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) { + super(injector.getHandler(), throttlePeriodMs, injector.getClock()); + mInjector = injector; } private boolean ensureInitialized() { @@ -281,19 +110,28 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { return false; } - mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.nativeIsSupportedFeature(); - mPowerStatsInternal = mPowerStatsSupplier.get(); - - if (mPowerStatsInternal != null) { - readCpuEnergyConsumerIds(); - } + mCpuScalingPolicies = mInjector.getCpuScalingPolicies(); + mPowerProfile = mInjector.getPowerProfile(); + mKernelCpuStatsReader = mInjector.getKernelCpuStatsReader(); + mUidResolver = mInjector.getUidResolver(); + mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever(); + mVoltageSupplier = mInjector.getVoltageSupplier(); + mDefaultCpuPowerBrackets = mInjector.getDefaultCpuPowerBrackets(); + mDefaultCpuPowerBracketsPerEnergyConsumer = + mInjector.getDefaultCpuPowerBracketsPerEnergyConsumer(); + + mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.isSupportedFeature(); + mCpuEnergyConsumerIds = + mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER); + mLastConsumedEnergyUws = new long[mCpuEnergyConsumerIds.length]; + Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount(); mCpuTimeByScalingStep = new long[cpuScalingStepCount]; mTempCpuTimeByScalingStep = new long[cpuScalingStepCount]; int[] scalingStepToPowerBracketMap = initPowerBrackets(); - mLayout = new CpuStatsArrayLayout(); + mLayout = new CpuPowerStatsLayout(); mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount); mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length); mLayout.addDeviceSectionUsageDuration(); @@ -306,7 +144,8 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { mLayout.toExtras(extras); mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, - mLayout.getDeviceStatsArrayLength(), mLayout.getUidStatsArrayLength(), extras); + mLayout.getDeviceStatsArrayLength(), /* stateLabels */null, + /* stateStatsArrayLength */ 0, mLayout.getUidStatsArrayLength(), extras); mCpuPowerStats = new PowerStats(mPowerStatsDescriptor); mTempUidStats = new long[mLayout.getCpuPowerBracketCount()]; @@ -315,32 +154,6 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { return true; } - private void readCpuEnergyConsumerIds() { - EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo(); - if (energyConsumerInfo == null) { - return; - } - - List<EnergyConsumer> cpuEnergyConsumers = new ArrayList<>(); - for (EnergyConsumer energyConsumer : energyConsumerInfo) { - if (energyConsumer.type == EnergyConsumerType.CPU_CLUSTER) { - cpuEnergyConsumers.add(energyConsumer); - } - } - if (cpuEnergyConsumers.isEmpty()) { - return; - } - - cpuEnergyConsumers.sort(Comparator.comparing(c -> c.ordinal)); - - mCpuEnergyConsumerIds = new int[cpuEnergyConsumers.size()]; - for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) { - mCpuEnergyConsumerIds[i] = cpuEnergyConsumers.get(i).id; - } - mLastConsumedEnergyUws = new long[cpuEnergyConsumers.size()]; - Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); - } - private int[] initPowerBrackets() { if (mPowerProfile.getCpuPowerBracketCount() != PowerProfile.POWER_BRACKETS_UNSPECIFIED) { return initPowerBracketsFromPowerProfile(); @@ -372,6 +185,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { return stepToBracketMap; } + private int[] initPowerBracketsByCluster(int defaultBracketCountPerCluster) { int[] stepToBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()]; int index = 0; @@ -531,7 +345,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { mCpuPowerStats.uidStats.clear(); // TODO(b/305120724): additionally retrieve time-in-cluster for each CPU cluster - long newTimestampNanos = mKernelCpuStatsReader.nativeReadCpuStats(this::processUidStats, + long newTimestampNanos = mKernelCpuStatsReader.readCpuStats(this::processUidStats, mLayout.getScalingStepToPowerBracketMap(), mLastUpdateTimestampNanos, mTempCpuTimeByScalingStep, mTempUidStats); for (int step = mLayout.getCpuScalingStepCount() - 1; step >= 0; step--) { @@ -571,35 +385,20 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; mLastVoltageMv = voltageMv; - CompletableFuture<EnergyConsumerResult[]> future = - mPowerStatsInternal.getEnergyConsumedAsync(mCpuEnergyConsumerIds); - EnergyConsumerResult[] results = null; - try { - results = future.get( - POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e); - } - if (results == null) { + long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mCpuEnergyConsumerIds); + if (energyUws == null) { return; } - for (int i = 0; i < mCpuEnergyConsumerIds.length; i++) { - int id = mCpuEnergyConsumerIds[i]; - for (EnergyConsumerResult result : results) { - if (result.id == id) { - long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED - ? result.energyUWs - mLastConsumedEnergyUws[i] : 0; - if (energyDelta < 0) { - // Likely, restart of powerstats HAL - energyDelta = 0; - } - mLayout.setConsumedEnergy(mCpuPowerStats.stats, i, - uJtoUc(energyDelta, averageVoltage)); - mLastConsumedEnergyUws[i] = result.energyUWs; - break; - } + for (int i = energyUws.length - 1; i >= 0; i--) { + long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED + ? energyUws[i] - mLastConsumedEnergyUws[i] : 0; + if (energyDelta < 0) { + // Likely, restart of powerstats HAL + energyDelta = 0; } + mLayout.setConsumedEnergy(mCpuPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage)); + mLastConsumedEnergyUws[i] = energyUws[i]; } } @@ -652,6 +451,17 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { * Native class that retrieves CPU stats from the kernel. */ public static class KernelCpuStatsReader { + protected boolean isSupportedFeature() { + return nativeIsSupportedFeature(); + } + + protected long readCpuStats(KernelCpuStatsCallback callback, + int[] scalingStepToPowerBracketMap, long lastUpdateTimestampNanos, + long[] outCpuTimeByScalingStep, long[] tempForUidStats) { + return nativeReadCpuStats(callback, scalingStepToPowerBracketMap, + lastUpdateTimestampNanos, outCpuTimeByScalingStep, tempForUidStats); + } + protected native boolean nativeIsSupportedFeature(); protected native long nativeReadCpuStats(KernelCpuStatsCallback callback, diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java new file mode 100644 index 000000000000..1bcb2c4bc5fa --- /dev/null +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.os.PersistableBundle; + +/** + * Captures the positions and lengths of sections of the stats array, such as time-in-state, + * power usage estimates etc. + */ +public class CpuPowerStatsLayout extends PowerStatsLayout { + private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION = "dt"; + private static final String EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT = "dtc"; + private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION = "dc"; + private static final String EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT = "dcc"; + private static final String EXTRA_UID_BRACKETS_POSITION = "ub"; + private static final String EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET = "us"; + + private int mDeviceCpuTimeByScalingStepPosition; + private int mDeviceCpuTimeByScalingStepCount; + private int mDeviceCpuTimeByClusterPosition; + private int mDeviceCpuTimeByClusterCount; + + private int mUidPowerBracketsPosition; + private int mUidPowerBracketCount; + + private int[] mScalingStepToPowerBracketMap; + + /** + * Declare that the stats array has a section capturing CPU time per scaling step + */ + public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) { + mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount); + mDeviceCpuTimeByScalingStepCount = scalingStepCount; + } + + public int getCpuScalingStepCount() { + return mDeviceCpuTimeByScalingStepCount; + } + + /** + * Saves the time duration in the <code>stats</code> element + * corresponding to the CPU scaling <code>state</code>. + */ + public void setTimeByScalingStep(long[] stats, int step, long value) { + stats[mDeviceCpuTimeByScalingStepPosition + step] = value; + } + + /** + * Extracts the time duration from the <code>stats</code> element + * corresponding to the CPU scaling <code>step</code>. + */ + public long getTimeByScalingStep(long[] stats, int step) { + return stats[mDeviceCpuTimeByScalingStepPosition + step]; + } + + /** + * Declare that the stats array has a section capturing CPU time in each cluster + */ + public void addDeviceSectionCpuTimeByCluster(int clusterCount) { + mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount); + mDeviceCpuTimeByClusterCount = clusterCount; + } + + public int getCpuClusterCount() { + return mDeviceCpuTimeByClusterCount; + } + + /** + * Saves the time duration in the <code>stats</code> element + * corresponding to the CPU <code>cluster</code>. + */ + public void setTimeByCluster(long[] stats, int cluster, long value) { + stats[mDeviceCpuTimeByClusterPosition + cluster] = value; + } + + /** + * Extracts the time duration from the <code>stats</code> element + * corresponding to the CPU <code>cluster</code>. + */ + public long getTimeByCluster(long[] stats, int cluster) { + return stats[mDeviceCpuTimeByClusterPosition + cluster]; + } + + /** + * Declare that the UID stats array has a section capturing CPU time per power bracket. + */ + public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) { + mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap; + updatePowerBracketCount(); + mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount); + } + + private void updatePowerBracketCount() { + mUidPowerBracketCount = 1; + for (int bracket : mScalingStepToPowerBracketMap) { + if (bracket >= mUidPowerBracketCount) { + mUidPowerBracketCount = bracket + 1; + } + } + } + + public int[] getScalingStepToPowerBracketMap() { + return mScalingStepToPowerBracketMap; + } + + public int getCpuPowerBracketCount() { + return mUidPowerBracketCount; + } + + /** + * Saves time in <code>bracket</code> in the corresponding section of <code>stats</code>. + */ + public void setUidTimeByPowerBracket(long[] stats, int bracket, long value) { + stats[mUidPowerBracketsPosition + bracket] = value; + } + + /** + * Extracts the time in <code>bracket</code> from a UID stats array. + */ + public long getUidTimeByPowerBracket(long[] stats, int bracket) { + return stats[mUidPowerBracketsPosition + bracket]; + } + + /** + * Copies the elements of the stats array layout into <code>extras</code> + */ + public void toExtras(PersistableBundle extras) { + super.toExtras(extras); + extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION, + mDeviceCpuTimeByScalingStepPosition); + extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT, + mDeviceCpuTimeByScalingStepCount); + extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION, + mDeviceCpuTimeByClusterPosition); + extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT, + mDeviceCpuTimeByClusterCount); + extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition); + putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET, + mScalingStepToPowerBracketMap); + } + + /** + * Retrieves elements of the stats array layout from <code>extras</code> + */ + public void fromExtras(PersistableBundle extras) { + super.fromExtras(extras); + mDeviceCpuTimeByScalingStepPosition = + extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION); + mDeviceCpuTimeByScalingStepCount = + extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT); + mDeviceCpuTimeByClusterPosition = + extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION); + mDeviceCpuTimeByClusterCount = + extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT); + mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION); + mScalingStepToPowerBracketMap = + getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET); + if (mScalingStepToPowerBracketMap == null) { + mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount]; + } + updatePowerBracketCount(); + } +} diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java index ed9414ff53a1..c34b8a8dc992 100644 --- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java @@ -29,8 +29,8 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; -public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProcessor { - private static final String TAG = "CpuAggregatedPowerStatsProcessor"; +public class CpuPowerStatsProcessor extends PowerStatsProcessor { + private static final String TAG = "CpuPowerStatsProcessor"; private static final double HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1); private static final int UNKNOWN = -1; @@ -64,7 +64,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces private PowerStats.Descriptor mLastUsedDescriptor; // Cached results of parsing of current PowerStats.Descriptor. Only refreshed when // mLastUsedDescriptor changes - private CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout; + private CpuPowerStatsLayout mStatsLayout; // Sequence of steps for power estimation and intermediate results. private PowerEstimationPlan mPlan; @@ -73,8 +73,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces // Temp array for retrieval of UID power stats, to avoid repeated allocations private long[] mTmpUidStatsArray; - public CpuAggregatedPowerStatsProcessor(PowerProfile powerProfile, - CpuScalingPolicies scalingPolicies) { + public CpuPowerStatsProcessor(PowerProfile powerProfile, CpuScalingPolicies scalingPolicies) { mCpuScalingPolicies = scalingPolicies; mCpuScalingStepCount = scalingPolicies.getScalingStepCount(); mScalingStepToCluster = new int[mCpuScalingStepCount]; @@ -106,7 +105,7 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces } mLastUsedDescriptor = descriptor; - mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); + mStatsLayout = new CpuPowerStatsLayout(); mStatsLayout.fromExtras(descriptor.extras); mTmpDeviceStatsArray = new long[descriptor.statsArrayLength]; @@ -527,6 +526,12 @@ public class CpuAggregatedPowerStatsProcessor extends AggregatedPowerStatsProces } @Override + String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { + // Unsupported for this power component + return null; + } + + @Override public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { unpackPowerStatsDescriptor(descriptor); StringBuilder sb = new StringBuilder(); diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java index 9ea143e5c201..c01363a9c7ba 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java @@ -387,92 +387,14 @@ public class MobileRadioPowerCalculator extends PowerCalculator { return consumptionMah; } - private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType, - @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, - int txLevel) { - long key = PowerProfile.SUBSYSTEM_MODEM; - - // Attach Modem drain type to the key if specified. - if (drainType != IGNORE) { - key |= drainType; - } - - // Attach RadioAccessTechnology to the key if specified. - switch (rat) { - case IGNORE: - // do nothing - break; - case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER: - key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT; - break; - case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE: - key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE; - break; - case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR: - key |= ModemPowerProfile.MODEM_RAT_TYPE_NR; - break; - default: - Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat); - } - - // Attach NR Frequency Range to the key if specified. - switch (freqRange) { - case IGNORE: - // do nothing - break; - case ServiceState.FREQUENCY_RANGE_UNKNOWN: - key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT; - break; - case ServiceState.FREQUENCY_RANGE_LOW: - key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW; - break; - case ServiceState.FREQUENCY_RANGE_MID: - key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID; - break; - case ServiceState.FREQUENCY_RANGE_HIGH: - key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH; - break; - case ServiceState.FREQUENCY_RANGE_MMWAVE: - key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE; - break; - default: - Log.w(TAG, "Unexpected NR frequency range : " + freqRange); - } - - // Attach transmission level to the key if specified. - switch (txLevel) { - case IGNORE: - // do nothing - break; - case 0: - key |= ModemPowerProfile.MODEM_TX_LEVEL_0; - break; - case 1: - key |= ModemPowerProfile.MODEM_TX_LEVEL_1; - break; - case 2: - key |= ModemPowerProfile.MODEM_TX_LEVEL_2; - break; - case 3: - key |= ModemPowerProfile.MODEM_TX_LEVEL_3; - break; - case 4: - key |= ModemPowerProfile.MODEM_TX_LEVEL_4; - break; - default: - Log.w(TAG, "Unexpected transmission level : " + txLevel); - } - return key; - } - /** * Calculates active receive radio power consumption (in milliamp-hours) from the given state's * duration. */ public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, long rxDurationMs) { - final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat, - freqRange, IGNORE); + final long rxKey = ModemPowerProfile.getAverageBatteryDrainKey( + ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat, freqRange, IGNORE); final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey, Double.NaN); if (Double.isNaN(drainRateMa)) { @@ -495,8 +417,8 @@ public class MobileRadioPowerCalculator extends PowerCalculator { */ public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) { - final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat, - freqRange, txLevel); + final long txKey = ModemPowerProfile.getAverageBatteryDrainKey( + ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat, freqRange, txLevel); final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey, Double.NaN); if (Double.isNaN(drainRateMa)) { diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java new file mode 100644 index 000000000000..8c154e4a0875 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.content.pm.PackageManager; +import android.hardware.power.stats.EnergyConsumerType; +import android.net.NetworkStats; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.OutcomeReceiver; +import android.os.PersistableBundle; +import android.telephony.AccessNetworkConstants; +import android.telephony.ModemActivityInfo; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.IntSupplier; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +public class MobileRadioPowerStatsCollector extends PowerStatsCollector { + private static final String TAG = "MobileRadioPowerStatsCollector"; + + /** + * The soonest the Mobile Radio stats can be updated due to a mobile radio power state change + * after it was last updated. + */ + @VisibleForTesting + protected static final long MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS = 1000 * 60 * 10; + + private static final long MODEM_ACTIVITY_REQUEST_TIMEOUT = 20000; + + private static final long ENERGY_UNSPECIFIED = -1; + + @VisibleForTesting + @AccessNetworkConstants.RadioAccessNetworkType + static final int[] NETWORK_TYPES = { + AccessNetworkConstants.AccessNetworkType.UNKNOWN, + AccessNetworkConstants.AccessNetworkType.GERAN, + AccessNetworkConstants.AccessNetworkType.UTRAN, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + AccessNetworkConstants.AccessNetworkType.CDMA2000, + AccessNetworkConstants.AccessNetworkType.IWLAN, + AccessNetworkConstants.AccessNetworkType.NGRAN + }; + + interface Injector { + Handler getHandler(); + Clock getClock(); + PowerStatsUidResolver getUidResolver(); + PackageManager getPackageManager(); + ConsumedEnergyRetriever getConsumedEnergyRetriever(); + IntSupplier getVoltageSupplier(); + Supplier<NetworkStats> getMobileNetworkStatsSupplier(); + TelephonyManager getTelephonyManager(); + LongSupplier getCallDurationSupplier(); + LongSupplier getPhoneSignalScanDurationSupplier(); + } + + private final Injector mInjector; + + private MobileRadioPowerStatsLayout mLayout; + private boolean mIsInitialized; + + private PowerStats mPowerStats; + private long[] mDeviceStats; + private PowerStatsUidResolver mPowerStatsUidResolver; + private volatile TelephonyManager mTelephonyManager; + private LongSupplier mCallDurationSupplier; + private LongSupplier mScanDurationSupplier; + private volatile Supplier<NetworkStats> mNetworkStatsSupplier; + private ConsumedEnergyRetriever mConsumedEnergyRetriever; + private IntSupplier mVoltageSupplier; + private int[] mEnergyConsumerIds = new int[0]; + private long mLastUpdateTimestampMillis; + private ModemActivityInfo mLastModemActivityInfo; + private NetworkStats mLastNetworkStats; + private long[] mLastConsumedEnergyUws; + private int mLastVoltageMv; + private long mLastCallDuration; + private long mLastScanDuration; + + public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) { + super(injector.getHandler(), throttlePeriodMs, injector.getClock()); + mInjector = injector; + } + + @Override + public void setEnabled(boolean enabled) { + if (enabled) { + PackageManager packageManager = mInjector.getPackageManager(); + super.setEnabled(packageManager != null + && packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)); + } else { + super.setEnabled(false); + } + } + + private boolean ensureInitialized() { + if (mIsInitialized) { + return true; + } + + if (!isEnabled()) { + return false; + } + + mPowerStatsUidResolver = mInjector.getUidResolver(); + mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever(); + mVoltageSupplier = mInjector.getVoltageSupplier(); + + mTelephonyManager = mInjector.getTelephonyManager(); + mNetworkStatsSupplier = mInjector.getMobileNetworkStatsSupplier(); + mCallDurationSupplier = mInjector.getCallDurationSupplier(); + mScanDurationSupplier = mInjector.getPhoneSignalScanDurationSupplier(); + + mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds( + EnergyConsumerType.MOBILE_RADIO); + mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length]; + Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); + + mLayout = new MobileRadioPowerStatsLayout(); + mLayout.addDeviceMobileActivity(); + mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length); + mLayout.addStateStats(); + mLayout.addUidNetworkStats(); + mLayout.addDeviceSectionUsageDuration(); + mLayout.addDeviceSectionPowerEstimate(); + mLayout.addUidSectionPowerEstimate(); + + SparseArray<String> stateLabels = new SparseArray<>(); + for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) { + final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR + ? ServiceState.FREQUENCY_RANGE_COUNT : 1; + for (int freq = 0; freq < freqCount; freq++) { + int stateKey = makeStateKey(rat, freq); + StringBuilder sb = new StringBuilder(); + if (rat != BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER) { + sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]); + } + if (freq != ServiceState.FREQUENCY_RANGE_UNKNOWN) { + if (!sb.isEmpty()) { + sb.append(" "); + } + sb.append(ServiceState.frequencyRangeToString(freq)); + } + stateLabels.put(stateKey, !sb.isEmpty() ? sb.toString() : "other"); + } + } + + PersistableBundle extras = new PersistableBundle(); + mLayout.toExtras(extras); + PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, mLayout.getDeviceStatsArrayLength(), + stateLabels, mLayout.getStateStatsArrayLength(), mLayout.getUidStatsArrayLength(), + extras); + mPowerStats = new PowerStats(powerStatsDescriptor); + mDeviceStats = mPowerStats.stats; + + mIsInitialized = true; + return true; + } + + @Override + protected PowerStats collectStats() { + if (!ensureInitialized()) { + return null; + } + + collectModemActivityInfo(); + + collectNetworkStats(); + + if (mEnergyConsumerIds.length != 0) { + collectEnergyConsumers(); + } + + if (mPowerStats.durationMs == 0) { + setTimestamp(mClock.elapsedRealtime()); + } + + return mPowerStats; + } + + private void collectModemActivityInfo() { + if (mTelephonyManager == null) { + return; + } + + CompletableFuture<ModemActivityInfo> immediateFuture = new CompletableFuture<>(); + mTelephonyManager.requestModemActivityInfo(Runnable::run, + new OutcomeReceiver<>() { + @Override + public void onResult(ModemActivityInfo result) { + immediateFuture.complete(result); + } + + @Override + public void onError(TelephonyManager.ModemActivityInfoException e) { + Slog.w(TAG, "error reading modem stats:" + e); + immediateFuture.complete(null); + } + }); + + ModemActivityInfo activityInfo; + try { + activityInfo = immediateFuture.get(MODEM_ACTIVITY_REQUEST_TIMEOUT, + TimeUnit.MILLISECONDS); + } catch (Exception e) { + Slog.e(TAG, "Cannot acquire ModemActivityInfo"); + activityInfo = null; + } + + ModemActivityInfo deltaInfo = mLastModemActivityInfo == null + ? (activityInfo == null ? null : activityInfo.getDelta(activityInfo)) + : mLastModemActivityInfo.getDelta(activityInfo); + + mLastModemActivityInfo = activityInfo; + + if (deltaInfo == null) { + return; + } + + setTimestamp(deltaInfo.getTimestampMillis()); + mLayout.setDeviceSleepTime(mDeviceStats, deltaInfo.getSleepTimeMillis()); + mLayout.setDeviceIdleTime(mDeviceStats, deltaInfo.getIdleTimeMillis()); + + long callDuration = mCallDurationSupplier.getAsLong(); + if (callDuration >= mLastCallDuration) { + mLayout.setDeviceCallTime(mDeviceStats, callDuration - mLastCallDuration); + } + mLastCallDuration = callDuration; + + long scanDuration = mScanDurationSupplier.getAsLong(); + if (scanDuration >= mLastScanDuration) { + mLayout.setDeviceScanTime(mDeviceStats, scanDuration - mLastScanDuration); + } + mLastScanDuration = scanDuration; + + SparseArray<long[]> stateStats = mPowerStats.stateStats; + stateStats.clear(); + + if (deltaInfo.getSpecificInfoLength() == 0) { + mLayout.addRxTxTimesForRat(stateStats, + AccessNetworkConstants.AccessNetworkType.UNKNOWN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, + deltaInfo.getReceiveTimeMillis(), + deltaInfo.getTransmitTimeMillis()); + } else { + for (int rat = 0; rat < NETWORK_TYPES.length; rat++) { + if (rat == AccessNetworkConstants.AccessNetworkType.NGRAN) { + for (int freq = 0; freq < ServiceState.FREQUENCY_RANGE_COUNT; freq++) { + mLayout.addRxTxTimesForRat(stateStats, rat, freq, + deltaInfo.getReceiveTimeMillis(rat, freq), + deltaInfo.getTransmitTimeMillis(rat, freq)); + } + } else { + mLayout.addRxTxTimesForRat(stateStats, rat, + ServiceState.FREQUENCY_RANGE_UNKNOWN, + deltaInfo.getReceiveTimeMillis(rat), + deltaInfo.getTransmitTimeMillis(rat)); + } + } + } + } + + private void collectNetworkStats() { + mPowerStats.uidStats.clear(); + + NetworkStats networkStats = mNetworkStatsSupplier.get(); + if (networkStats == null) { + return; + } + + List<BatteryStatsImpl.NetworkStatsDelta> delta = + BatteryStatsImpl.computeDelta(networkStats, mLastNetworkStats); + mLastNetworkStats = networkStats; + for (int i = delta.size() - 1; i >= 0; i--) { + BatteryStatsImpl.NetworkStatsDelta uidDelta = delta.get(i); + long rxBytes = uidDelta.getRxBytes(); + long txBytes = uidDelta.getTxBytes(); + long rxPackets = uidDelta.getRxPackets(); + long txPackets = uidDelta.getTxPackets(); + if (rxBytes == 0 && txBytes == 0 && rxPackets == 0 && txPackets == 0) { + continue; + } + + int uid = mPowerStatsUidResolver.mapUid(uidDelta.getUid()); + long[] stats = mPowerStats.uidStats.get(uid); + if (stats == null) { + stats = new long[mLayout.getUidStatsArrayLength()]; + mPowerStats.uidStats.put(uid, stats); + mLayout.setUidRxBytes(stats, rxBytes); + mLayout.setUidTxBytes(stats, txBytes); + mLayout.setUidRxPackets(stats, rxPackets); + mLayout.setUidTxPackets(stats, txPackets); + } else { + mLayout.setUidRxBytes(stats, mLayout.getUidRxBytes(stats) + rxBytes); + mLayout.setUidTxBytes(stats, mLayout.getUidTxBytes(stats) + txBytes); + mLayout.setUidRxPackets(stats, mLayout.getUidRxPackets(stats) + rxPackets); + mLayout.setUidTxPackets(stats, mLayout.getUidTxPackets(stats) + txPackets); + } + } + } + + private void collectEnergyConsumers() { + int voltageMv = mVoltageSupplier.getAsInt(); + if (voltageMv <= 0) { + Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv + + " mV) when querying energy consumers"); + return; + } + + int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; + mLastVoltageMv = voltageMv; + + long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds); + if (energyUws == null) { + return; + } + + for (int i = energyUws.length - 1; i >= 0; i--) { + long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED + ? energyUws[i] - mLastConsumedEnergyUws[i] : 0; + if (energyDelta < 0) { + // Likely, restart of powerstats HAL + energyDelta = 0; + } + mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage)); + mLastConsumedEnergyUws[i] = energyUws[i]; + } + } + + static int makeStateKey(int rat, int freqRange) { + if (rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR) { + return rat | (freqRange << 8); + } else { + return rat; + } + } + + private void setTimestamp(long timestamp) { + mPowerStats.durationMs = Math.max(timestamp - mLastUpdateTimestampMillis, 0); + mLastUpdateTimestampMillis = timestamp; + } + + @BatteryStats.RadioAccessTechnology + static int mapRadioAccessNetworkTypeToRadioAccessTechnology( + @AccessNetworkConstants.RadioAccessNetworkType int networkType) { + switch (networkType) { + case AccessNetworkConstants.AccessNetworkType.NGRAN: + return BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR; + case AccessNetworkConstants.AccessNetworkType.EUTRAN: + return BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE; + case AccessNetworkConstants.AccessNetworkType.UNKNOWN: //fallthrough + case AccessNetworkConstants.AccessNetworkType.GERAN: //fallthrough + case AccessNetworkConstants.AccessNetworkType.UTRAN: //fallthrough + case AccessNetworkConstants.AccessNetworkType.CDMA2000: //fallthrough + case AccessNetworkConstants.AccessNetworkType.IWLAN: + return BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER; + default: + Slog.w(TAG, + "Unhandled RadioAccessNetworkType (" + networkType + "), mapping to OTHER"); + return BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER; + } + } +} diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java new file mode 100644 index 000000000000..81d7c2fa2880 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.annotation.NonNull; +import android.os.PersistableBundle; +import android.telephony.ModemActivityInfo; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.os.PowerStats; + +/** + * Captures the positions and lengths of sections of the stats array, such as time-in-state, + * power usage estimates etc. + */ +class MobileRadioPowerStatsLayout extends PowerStatsLayout { + private static final String TAG = "MobileRadioPowerStatsLayout"; + private static final String EXTRA_DEVICE_SLEEP_TIME_POSITION = "dt-sleep"; + private static final String EXTRA_DEVICE_IDLE_TIME_POSITION = "dt-idle"; + private static final String EXTRA_DEVICE_SCAN_TIME_POSITION = "dt-scan"; + private static final String EXTRA_DEVICE_CALL_TIME_POSITION = "dt-call"; + private static final String EXTRA_DEVICE_CALL_POWER_POSITION = "dp-call"; + private static final String EXTRA_STATE_RX_TIME_POSITION = "srx"; + private static final String EXTRA_STATE_TX_TIMES_POSITION = "stx"; + private static final String EXTRA_STATE_TX_TIMES_COUNT = "stxc"; + private static final String EXTRA_UID_RX_BYTES_POSITION = "urxb"; + private static final String EXTRA_UID_TX_BYTES_POSITION = "utxb"; + private static final String EXTRA_UID_RX_PACKETS_POSITION = "urxp"; + private static final String EXTRA_UID_TX_PACKETS_POSITION = "utxp"; + + private int mDeviceSleepTimePosition; + private int mDeviceIdleTimePosition; + private int mDeviceScanTimePosition; + private int mDeviceCallTimePosition; + private int mDeviceCallPowerPosition; + private int mStateRxTimePosition; + private int mStateTxTimesPosition; + private int mStateTxTimesCount; + private int mUidRxBytesPosition; + private int mUidTxBytesPosition; + private int mUidRxPacketsPosition; + private int mUidTxPacketsPosition; + + MobileRadioPowerStatsLayout() { + } + + MobileRadioPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) { + super(descriptor); + } + + void addDeviceMobileActivity() { + mDeviceSleepTimePosition = addDeviceSection(1); + mDeviceIdleTimePosition = addDeviceSection(1); + mDeviceScanTimePosition = addDeviceSection(1); + mDeviceCallTimePosition = addDeviceSection(1); + } + + void addStateStats() { + mStateRxTimePosition = addStateSection(1); + mStateTxTimesCount = ModemActivityInfo.getNumTxPowerLevels(); + mStateTxTimesPosition = addStateSection(mStateTxTimesCount); + } + + void addUidNetworkStats() { + mUidRxBytesPosition = addUidSection(1); + mUidTxBytesPosition = addUidSection(1); + mUidRxPacketsPosition = addUidSection(1); + mUidTxPacketsPosition = addUidSection(1); + } + + @Override + public void addDeviceSectionPowerEstimate() { + super.addDeviceSectionPowerEstimate(); + mDeviceCallPowerPosition = addDeviceSection(1); + } + + public void setDeviceSleepTime(long[] stats, long durationMillis) { + stats[mDeviceSleepTimePosition] = durationMillis; + } + + public long getDeviceSleepTime(long[] stats) { + return stats[mDeviceSleepTimePosition]; + } + + public void setDeviceIdleTime(long[] stats, long durationMillis) { + stats[mDeviceIdleTimePosition] = durationMillis; + } + + public long getDeviceIdleTime(long[] stats) { + return stats[mDeviceIdleTimePosition]; + } + + public void setDeviceScanTime(long[] stats, long durationMillis) { + stats[mDeviceScanTimePosition] = durationMillis; + } + + public long getDeviceScanTime(long[] stats) { + return stats[mDeviceScanTimePosition]; + } + + public void setDeviceCallTime(long[] stats, long durationMillis) { + stats[mDeviceCallTimePosition] = durationMillis; + } + + public long getDeviceCallTime(long[] stats) { + return stats[mDeviceCallTimePosition]; + } + + public void setDeviceCallPowerEstimate(long[] stats, double power) { + stats[mDeviceCallPowerPosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + public double getDeviceCallPowerEstimate(long[] stats) { + return stats[mDeviceCallPowerPosition] / MILLI_TO_NANO_MULTIPLIER; + } + + public void setStateRxTime(long[] stats, long durationMillis) { + stats[mStateRxTimePosition] = durationMillis; + } + + public long getStateRxTime(long[] stats) { + return stats[mStateRxTimePosition]; + } + + public void setStateTxTime(long[] stats, int level, int durationMillis) { + stats[mStateTxTimesPosition + level] = durationMillis; + } + + public long getStateTxTime(long[] stats, int level) { + return stats[mStateTxTimesPosition + level]; + } + + public void setUidRxBytes(long[] stats, long count) { + stats[mUidRxBytesPosition] = count; + } + + public long getUidRxBytes(long[] stats) { + return stats[mUidRxBytesPosition]; + } + + public void setUidTxBytes(long[] stats, long count) { + stats[mUidTxBytesPosition] = count; + } + + public long getUidTxBytes(long[] stats) { + return stats[mUidTxBytesPosition]; + } + + public void setUidRxPackets(long[] stats, long count) { + stats[mUidRxPacketsPosition] = count; + } + + public long getUidRxPackets(long[] stats) { + return stats[mUidRxPacketsPosition]; + } + + public void setUidTxPackets(long[] stats, long count) { + stats[mUidTxPacketsPosition] = count; + } + + public long getUidTxPackets(long[] stats) { + return stats[mUidTxPacketsPosition]; + } + + /** + * Copies the elements of the stats array layout into <code>extras</code> + */ + public void toExtras(PersistableBundle extras) { + super.toExtras(extras); + extras.putInt(EXTRA_DEVICE_SLEEP_TIME_POSITION, mDeviceSleepTimePosition); + extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition); + extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition); + extras.putInt(EXTRA_DEVICE_CALL_TIME_POSITION, mDeviceCallTimePosition); + extras.putInt(EXTRA_DEVICE_CALL_POWER_POSITION, mDeviceCallPowerPosition); + extras.putInt(EXTRA_STATE_RX_TIME_POSITION, mStateRxTimePosition); + extras.putInt(EXTRA_STATE_TX_TIMES_POSITION, mStateTxTimesPosition); + extras.putInt(EXTRA_STATE_TX_TIMES_COUNT, mStateTxTimesCount); + extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition); + extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition); + extras.putInt(EXTRA_UID_RX_PACKETS_POSITION, mUidRxPacketsPosition); + extras.putInt(EXTRA_UID_TX_PACKETS_POSITION, mUidTxPacketsPosition); + } + + /** + * Retrieves elements of the stats array layout from <code>extras</code> + */ + public void fromExtras(PersistableBundle extras) { + super.fromExtras(extras); + mDeviceSleepTimePosition = extras.getInt(EXTRA_DEVICE_SLEEP_TIME_POSITION); + mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION); + mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION); + mDeviceCallTimePosition = extras.getInt(EXTRA_DEVICE_CALL_TIME_POSITION); + mDeviceCallPowerPosition = extras.getInt(EXTRA_DEVICE_CALL_POWER_POSITION); + mStateRxTimePosition = extras.getInt(EXTRA_STATE_RX_TIME_POSITION); + mStateTxTimesPosition = extras.getInt(EXTRA_STATE_TX_TIMES_POSITION); + mStateTxTimesCount = extras.getInt(EXTRA_STATE_TX_TIMES_COUNT); + mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION); + mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION); + mUidRxPacketsPosition = extras.getInt(EXTRA_UID_RX_PACKETS_POSITION); + mUidTxPacketsPosition = extras.getInt(EXTRA_UID_TX_PACKETS_POSITION); + } + + public void addRxTxTimesForRat(SparseArray<long[]> stateStats, int networkType, int freqRange, + long rxTime, int[] txTime) { + if (txTime.length != mStateTxTimesCount) { + Slog.wtf(TAG, "Invalid TX time array size: " + txTime.length); + return; + } + + boolean nonZero = false; + if (rxTime != 0) { + nonZero = true; + } else { + for (int i = txTime.length - 1; i >= 0; i--) { + if (txTime[i] != 0) { + nonZero = true; + break; + } + } + } + + if (!nonZero) { + return; + } + + int rat = MobileRadioPowerStatsCollector.mapRadioAccessNetworkTypeToRadioAccessTechnology( + networkType); + int stateKey = MobileRadioPowerStatsCollector.makeStateKey(rat, freqRange); + long[] stats = stateStats.get(stateKey); + if (stats == null) { + stats = new long[getStateStatsArrayLength()]; + stateStats.put(stateKey, stats); + } + + stats[mStateRxTimePosition] += rxTime; + for (int i = mStateTxTimesCount - 1; i >= 0; i--) { + stats[mStateTxTimesPosition + i] += txTime[i]; + } + } +} diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java new file mode 100644 index 000000000000..c97c64bafcba --- /dev/null +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.power.stats; + +import android.os.BatteryStats; +import android.telephony.CellSignalStrength; +import android.telephony.ModemActivityInfo; +import android.telephony.ServiceState; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; +import com.android.internal.power.ModemPowerProfile; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MobileRadioPowerStatsProcessor extends PowerStatsProcessor { + private static final String TAG = "MobileRadioPowerStatsProcessor"; + private static final boolean DEBUG = false; + + private static final int NUM_SIGNAL_STRENGTH_LEVELS = + CellSignalStrength.getNumSignalStrengthLevels(); + private static final int IGNORE = -1; + + private final UsageBasedPowerEstimator mSleepPowerEstimator; + private final UsageBasedPowerEstimator mIdlePowerEstimator; + private final UsageBasedPowerEstimator mCallPowerEstimator; + private final UsageBasedPowerEstimator mScanPowerEstimator; + + private static class RxTxPowerEstimators { + UsageBasedPowerEstimator mRxPowerEstimator; + UsageBasedPowerEstimator[] mTxPowerEstimators = + new UsageBasedPowerEstimator[ModemActivityInfo.getNumTxPowerLevels()]; + } + + private final SparseArray<RxTxPowerEstimators> mRxTxPowerEstimators = new SparseArray<>(); + + private PowerStats.Descriptor mLastUsedDescriptor; + private MobileRadioPowerStatsLayout mStatsLayout; + // Sequence of steps for power estimation and intermediate results. + private PowerEstimationPlan mPlan; + + private long[] mTmpDeviceStatsArray; + private long[] mTmpStateStatsArray; + private long[] mTmpUidStatsArray; + + public MobileRadioPowerStatsProcessor(PowerProfile powerProfile) { + final double sleepDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP, + Double.NaN); + if (Double.isNaN(sleepDrainRateMa)) { + mSleepPowerEstimator = null; + } else { + mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa); + } + + final double idleDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE, + Double.NaN); + if (Double.isNaN(idleDrainRateMa)) { + mIdlePowerEstimator = null; + } else { + mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa); + } + + // Instantiate legacy power estimators + double powerRadioActiveMa = + powerProfile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN); + if (Double.isNaN(powerRadioActiveMa)) { + double sum = 0; + sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); + for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) { + sum += powerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i); + } + powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1); + } + mCallPowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa); + + mScanPowerEstimator = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0)); + + for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) { + final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR + ? ServiceState.FREQUENCY_RANGE_COUNT : 1; + for (int freqRange = 0; freqRange < freqCount; freqRange++) { + mRxTxPowerEstimators.put( + MobileRadioPowerStatsCollector.makeStateKey(rat, freqRange), + buildRxTxPowerEstimators(powerProfile, rat, freqRange)); + } + } + } + + private static RxTxPowerEstimators buildRxTxPowerEstimators(PowerProfile powerProfile, int rat, + int freqRange) { + RxTxPowerEstimators estimators = new RxTxPowerEstimators(); + long rxKey = ModemPowerProfile.getAverageBatteryDrainKey( + ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat, freqRange, IGNORE); + double rxDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa(rxKey, Double.NaN); + if (Double.isNaN(rxDrainRateMa)) { + Log.w(TAG, "Unavailable Power Profile constant for key 0x" + + Long.toHexString(rxKey)); + rxDrainRateMa = 0; + } + estimators.mRxPowerEstimator = new UsageBasedPowerEstimator(rxDrainRateMa); + for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) { + long txKey = ModemPowerProfile.getAverageBatteryDrainKey( + ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat, freqRange, txLevel); + double txDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa(txKey, + Double.NaN); + if (Double.isNaN(txDrainRateMa)) { + Log.w(TAG, "Unavailable Power Profile constant for key 0x" + + Long.toHexString(txKey)); + txDrainRateMa = 0; + } + estimators.mTxPowerEstimators[txLevel] = new UsageBasedPowerEstimator(txDrainRateMa); + } + return estimators; + } + + private static class Intermediates { + /** + * Number of received packets + */ + public long rxPackets; + /** + * Number of transmitted packets + */ + public long txPackets; + /** + * Estimated power for the RX state of the modem. + */ + public double rxPower; + /** + * Estimated power for the TX state of the modem. + */ + public double txPower; + /** + * Estimated power for IDLE, SLEEP and CELL-SCAN states of the modem. + */ + public double inactivePower; + /** + * Estimated power for IDLE, SLEEP and CELL-SCAN states of the modem. + */ + public double callPower; + /** + * Measured consumed energy from power monitoring hardware (micro-coulombs) + */ + public long consumedEnergy; + } + + @Override + void finish(PowerComponentAggregatedPowerStats stats) { + if (stats.getPowerStatsDescriptor() == null) { + return; + } + + unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor()); + + if (mPlan == null) { + mPlan = new PowerEstimationPlan(stats.getConfig()); + } + + for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) { + DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i); + Intermediates intermediates = new Intermediates(); + estimation.intermediates = intermediates; + computeDevicePowerEstimates(stats, estimation.stateValues, intermediates); + } + + if (mStatsLayout.getEnergyConsumerCount() != 0) { + double ratio = computeEstimateAdjustmentRatioUsingConsumedEnergy(); + if (ratio != 1) { + for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) { + DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i); + adjustDevicePowerEstimates(stats, estimation.stateValues, + (Intermediates) estimation.intermediates, ratio); + } + } + } + + combineDeviceStateEstimates(); + + ArrayList<Integer> uids = new ArrayList<>(); + stats.collectUids(uids); + if (!uids.isEmpty()) { + for (int uid : uids) { + for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { + computeUidRxTxTotals(stats, uid, mPlan.uidStateEstimates.get(i)); + } + } + + for (int uid : uids) { + for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { + computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i)); + } + } + } + mPlan.resetIntermediates(); + } + + private void unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) { + if (descriptor.equals(mLastUsedDescriptor)) { + return; + } + + mLastUsedDescriptor = descriptor; + mStatsLayout = new MobileRadioPowerStatsLayout(descriptor); + mTmpDeviceStatsArray = new long[descriptor.statsArrayLength]; + mTmpStateStatsArray = new long[descriptor.stateStatsArrayLength]; + mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength]; + } + + /** + * Compute power estimates using the power profile. + */ + private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats, + int[] deviceStates, Intermediates intermediates) { + if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStates)) { + return; + } + + for (int i = mStatsLayout.getEnergyConsumerCount() - 1; i >= 0; i--) { + intermediates.consumedEnergy += mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, i); + } + + if (mSleepPowerEstimator != null) { + intermediates.inactivePower += mSleepPowerEstimator.calculatePower( + mStatsLayout.getDeviceSleepTime(mTmpDeviceStatsArray)); + } + + if (mIdlePowerEstimator != null) { + intermediates.inactivePower += mIdlePowerEstimator.calculatePower( + mStatsLayout.getDeviceIdleTime(mTmpDeviceStatsArray)); + } + + if (mScanPowerEstimator != null) { + intermediates.inactivePower += mScanPowerEstimator.calculatePower( + mStatsLayout.getDeviceScanTime(mTmpDeviceStatsArray)); + } + + stats.forEachStateStatsKey(key -> { + RxTxPowerEstimators estimators = mRxTxPowerEstimators.get(key); + stats.getStateStats(mTmpStateStatsArray, key, deviceStates); + long rxTime = mStatsLayout.getStateRxTime(mTmpStateStatsArray); + intermediates.rxPower += estimators.mRxPowerEstimator.calculatePower(rxTime); + for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) { + long txTime = mStatsLayout.getStateTxTime(mTmpStateStatsArray, txLevel); + intermediates.txPower += + estimators.mTxPowerEstimators[txLevel].calculatePower(txTime); + } + }); + + if (mCallPowerEstimator != null) { + intermediates.callPower = mCallPowerEstimator.calculatePower( + mStatsLayout.getDeviceCallTime(mTmpDeviceStatsArray)); + } + + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, + intermediates.rxPower + intermediates.txPower + intermediates.inactivePower); + mStatsLayout.setDeviceCallPowerEstimate(mTmpDeviceStatsArray, intermediates.callPower); + stats.setDeviceStats(deviceStates, mTmpDeviceStatsArray); + } + + /** + * Compute an adjustment ratio using the total power estimated using the power profile + * and the total power measured by hardware. + */ + private double computeEstimateAdjustmentRatioUsingConsumedEnergy() { + long totalConsumedEnergy = 0; + double totalPower = 0; + + for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) { + Intermediates intermediates = + (Intermediates) mPlan.deviceStateEstimations.get(i).intermediates; + totalPower += intermediates.rxPower + intermediates.txPower + + intermediates.inactivePower + intermediates.callPower; + totalConsumedEnergy += intermediates.consumedEnergy; + } + + if (totalPower == 0) { + return 1; + } + + return uCtoMah(totalConsumedEnergy) / totalPower; + } + + /** + * Uniformly apply the same adjustment to all power estimates in order to ensure that the total + * estimated power matches the measured consumed power. We are not claiming that all + * averages captured in the power profile have to be off by the same percentage in reality. + */ + private void adjustDevicePowerEstimates(PowerComponentAggregatedPowerStats stats, + int[] deviceStates, Intermediates intermediates, double ratio) { + intermediates.rxPower *= ratio; + intermediates.txPower *= ratio; + intermediates.inactivePower *= ratio; + intermediates.callPower *= ratio; + + if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStates)) { + return; + } + + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, + intermediates.rxPower + intermediates.txPower + intermediates.inactivePower); + mStatsLayout.setDeviceCallPowerEstimate(mTmpDeviceStatsArray, intermediates.callPower); + stats.setDeviceStats(deviceStates, mTmpDeviceStatsArray); + } + + /** + * This step is effectively a no-op in the cases where we track the same states for + * the entire device and all UIDs (e.g. screen on/off, on-battery/on-charger etc). However, + * if the lists of tracked states are not the same, we need to combine some estimates + * before distributing them proportionally to UIDs. + */ + private void combineDeviceStateEstimates() { + for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) { + CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i); + Intermediates cdseIntermediates = new Intermediates(); + cdse.intermediates = cdseIntermediates; + List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations; + for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) { + DeviceStateEstimation dse = deviceStateEstimations.get(j); + Intermediates intermediates = (Intermediates) dse.intermediates; + cdseIntermediates.rxPower += intermediates.rxPower; + cdseIntermediates.txPower += intermediates.txPower; + cdseIntermediates.inactivePower += intermediates.inactivePower; + cdseIntermediates.consumedEnergy += intermediates.consumedEnergy; + } + } + } + + private void computeUidRxTxTotals(PowerComponentAggregatedPowerStats stats, int uid, + UidStateEstimate uidStateEstimate) { + Intermediates intermediates = + (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates; + for (UidStateProportionalEstimate proportionalEstimate : + uidStateEstimate.proportionalEstimates) { + if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { + continue; + } + + intermediates.rxPackets += mStatsLayout.getUidRxPackets(mTmpUidStatsArray); + intermediates.txPackets += mStatsLayout.getUidTxPackets(mTmpUidStatsArray); + } + } + + private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, int uid, + UidStateEstimate uidStateEstimate) { + Intermediates intermediates = + (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates; + for (UidStateProportionalEstimate proportionalEstimate : + uidStateEstimate.proportionalEstimates) { + if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { + continue; + } + + double power = 0; + if (intermediates.rxPackets != 0) { + power += intermediates.rxPower * mStatsLayout.getUidRxPackets(mTmpUidStatsArray) + / intermediates.rxPackets; + } + if (intermediates.txPackets != 0) { + power += intermediates.txPower * mStatsLayout.getUidTxPackets(mTmpUidStatsArray) + / intermediates.txPackets; + } + + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + + if (DEBUG) { + Slog.d(TAG, "UID: " + uid + + " states: " + Arrays.toString(proportionalEstimate.stateValues) + + " stats: " + Arrays.toString(mTmpUidStatsArray) + + " rx: " + mStatsLayout.getUidRxPackets(mTmpUidStatsArray) + + " rx-power: " + intermediates.rxPower + + " rx-packets: " + intermediates.rxPackets + + " tx: " + mStatsLayout.getUidTxPackets(mTmpUidStatsArray) + + " tx-power: " + intermediates.txPower + + " tx-packets: " + intermediates.txPackets + + " power: " + power); + } + } + } + + @Override + String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + unpackPowerStatsDescriptor(descriptor); + return "idle: " + mStatsLayout.getDeviceIdleTime(stats) + + " sleep: " + mStatsLayout.getDeviceSleepTime(stats) + + " scan: " + mStatsLayout.getDeviceScanTime(stats) + + " power: " + mStatsLayout.getDevicePowerEstimate(stats); + } + + @Override + String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { + unpackPowerStatsDescriptor(descriptor); + StringBuilder sb = new StringBuilder(); + sb.append(descriptor.getStateLabel(key)); + sb.append(" rx: ").append(mStatsLayout.getStateRxTime(stats)); + sb.append(" tx: "); + for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) { + if (txLevel != 0) { + sb.append(", "); + } + sb.append(mStatsLayout.getStateTxTime(stats, txLevel)); + } + return sb.toString(); + } + + @Override + String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + unpackPowerStatsDescriptor(descriptor); + return "rx: " + mStatsLayout.getUidRxPackets(stats) + + " tx: " + mStatsLayout.getUidTxPackets(stats) + + " power: " + mStatsLayout.getUidPowerEstimate(stats); + } +} diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java index 935695008a9a..6c4a2b6e6359 100644 --- a/services/core/java/com/android/server/power/stats/MultiStateStats.java +++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java @@ -288,6 +288,14 @@ public class MultiStateStats { } /** + * Copies time-in-state and timestamps from the supplied prototype. Does not + * copy accumulated counts. + */ + public void copyStatesFrom(MultiStateStats otherStats) { + mCounter.copyStatesFrom(otherStats.mCounter); + } + + /** * Updates the current composite state by changing one of the States supplied to the Factory * constructor. * diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java new file mode 100644 index 000000000000..62b653f61373 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.power.stats; + +import android.os.BatteryConsumer; +import android.os.PersistableBundle; + +import com.android.internal.os.PowerStats; + +public class PhoneCallPowerStatsProcessor extends PowerStatsProcessor { + private final PowerStatsLayout mStatsLayout; + private final PowerStats.Descriptor mDescriptor; + private final long[] mTmpDeviceStats; + private PowerStats.Descriptor mMobileRadioStatsDescriptor; + private MobileRadioPowerStatsLayout mMobileRadioStatsLayout; + private long[] mTmpMobileRadioDeviceStats; + + public PhoneCallPowerStatsProcessor() { + mStatsLayout = new PowerStatsLayout(); + mStatsLayout.addDeviceSectionPowerEstimate(); + PersistableBundle extras = new PersistableBundle(); + mStatsLayout.toExtras(extras); + mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_PHONE, + mStatsLayout.getDeviceStatsArrayLength(), null, 0, 0, extras); + mTmpDeviceStats = new long[mDescriptor.statsArrayLength]; + } + + @Override + void finish(PowerComponentAggregatedPowerStats stats) { + stats.setPowerStatsDescriptor(mDescriptor); + + PowerComponentAggregatedPowerStats mobileRadioStats = + stats.getAggregatedPowerStats().getPowerComponentStats( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO); + if (mobileRadioStats == null) { + return; + } + + if (mMobileRadioStatsDescriptor == null) { + mMobileRadioStatsDescriptor = mobileRadioStats.getPowerStatsDescriptor(); + if (mMobileRadioStatsDescriptor == null) { + return; + } + + mMobileRadioStatsLayout = + new MobileRadioPowerStatsLayout( + mMobileRadioStatsDescriptor); + mTmpMobileRadioDeviceStats = new long[mMobileRadioStatsDescriptor.statsArrayLength]; + } + + MultiStateStats.States[] deviceStateConfig = + mobileRadioStats.getConfig().getDeviceStateConfig(); + + // Phone call power estimates have already been calculated by the mobile radio stats + // processor. All that remains to be done is copy the estimates over. + MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig, + states -> { + mobileRadioStats.getDeviceStats(mTmpMobileRadioDeviceStats, states); + double callPowerEstimate = + mMobileRadioStatsLayout.getDeviceCallPowerEstimate( + mTmpMobileRadioDeviceStats); + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, callPowerEstimate); + stats.setDeviceStats(states, mTmpDeviceStats); + }); + } + + @Override + String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + return "power: " + mStatsLayout.getDevicePowerEstimate(stats); + } + + @Override + String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { + // Unsupported for this power component + return null; + } + + @Override + String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { + // Unsupported for this power component + return null; + } +} 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 1637022f705d..6d58307dbefa 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -18,6 +18,7 @@ package com.android.server.power.stats; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.UserHandle; import android.util.IndentingPrintWriter; import android.util.SparseArray; @@ -29,7 +30,10 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.StringWriter; +import java.util.Arrays; import java.util.Collection; +import java.util.function.IntConsumer; /** * Aggregated power stats for a specific power component (e.g. CPU, WiFi, etc). This class @@ -41,22 +45,28 @@ class PowerComponentAggregatedPowerStats { static final String XML_TAG_POWER_COMPONENT = "power_component"; static final String XML_ATTR_ID = "id"; private static final String XML_TAG_DEVICE_STATS = "device-stats"; + private static final String XML_TAG_STATE_STATS = "state-stats"; + private static final String XML_ATTR_KEY = "key"; private static final String XML_TAG_UID_STATS = "uid-stats"; private static final String XML_ATTR_UID = "uid"; private static final long UNKNOWN = -1; public final int powerComponentId; - private final MultiStateStats.States[] mDeviceStateConfig; - private final MultiStateStats.States[] mUidStateConfig; + @NonNull + private final AggregatedPowerStats mAggregatedPowerStats; @NonNull private final AggregatedPowerStatsConfig.PowerComponent mConfig; + private final MultiStateStats.States[] mDeviceStateConfig; + private final MultiStateStats.States[] mUidStateConfig; private final int[] mDeviceStates; private MultiStateStats.Factory mStatsFactory; + private MultiStateStats.Factory mStateStatsFactory; private MultiStateStats.Factory mUidStatsFactory; private PowerStats.Descriptor mPowerStatsDescriptor; private long mPowerStatsTimestamp; private MultiStateStats mDeviceStats; + private final SparseArray<MultiStateStats> mStateStats = new SparseArray<>(); private final SparseArray<UidStats> mUidStats = new SparseArray<>(); private static class UidStats { @@ -64,7 +74,9 @@ class PowerComponentAggregatedPowerStats { public MultiStateStats stats; } - PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) { + PowerComponentAggregatedPowerStats(@NonNull AggregatedPowerStats aggregatedPowerStats, + @NonNull AggregatedPowerStatsConfig.PowerComponent config) { + mAggregatedPowerStats = aggregatedPowerStats; mConfig = config; powerComponentId = config.getPowerComponentId(); mDeviceStateConfig = config.getDeviceStateConfig(); @@ -74,6 +86,11 @@ class PowerComponentAggregatedPowerStats { } @NonNull + AggregatedPowerStats getAggregatedPowerStats() { + return mAggregatedPowerStats; + } + + @NonNull public AggregatedPowerStatsConfig.PowerComponent getConfig() { return mConfig; } @@ -83,16 +100,25 @@ class PowerComponentAggregatedPowerStats { return mPowerStatsDescriptor; } - void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) { + public void setPowerStatsDescriptor(PowerStats.Descriptor powerStatsDescriptor) { + mPowerStatsDescriptor = powerStatsDescriptor; + } + + void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, + long timestampMs) { if (mDeviceStats == null) { - createDeviceStats(); + createDeviceStats(timestampMs); } mDeviceStates[stateId] = state; if (mDeviceStateConfig[stateId].isTracked()) { if (mDeviceStats != null) { - mDeviceStats.setState(stateId, state, time); + mDeviceStats.setState(stateId, state, timestampMs); + } + for (int i = mStateStats.size() - 1; i >= 0; i--) { + MultiStateStats stateStats = mStateStats.valueAt(i); + stateStats.setState(stateId, state, timestampMs); } } @@ -100,36 +126,39 @@ class PowerComponentAggregatedPowerStats { for (int i = mUidStats.size() - 1; i >= 0; i--) { PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i); if (uidStats.stats == null) { - createUidStats(uidStats); + createUidStats(uidStats, timestampMs); } uidStats.states[stateId] = state; if (uidStats.stats != null) { - uidStats.stats.setState(stateId, state, time); + uidStats.stats.setState(stateId, state, timestampMs); } } } } void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state, - long time) { + long timestampMs) { if (!mUidStateConfig[stateId].isTracked()) { return; } UidStats uidStats = getUidStats(uid); if (uidStats.stats == null) { - createUidStats(uidStats); + createUidStats(uidStats, timestampMs); } uidStats.states[stateId] = state; if (uidStats.stats != null) { - uidStats.stats.setState(stateId, state, time); + uidStats.stats.setState(stateId, state, timestampMs); } } void setDeviceStats(@AggregatedPowerStatsConfig.TrackedState int[] states, long[] values) { + if (mDeviceStats == null) { + createDeviceStats(0); + } mDeviceStats.setStats(states, values); } @@ -147,16 +176,24 @@ class PowerComponentAggregatedPowerStats { mPowerStatsDescriptor = powerStats.descriptor; if (mDeviceStats == null) { - createDeviceStats(); + createDeviceStats(timestampMs); } + for (int i = powerStats.stateStats.size() - 1; i >= 0; i--) { + int key = powerStats.stateStats.keyAt(i); + MultiStateStats stateStats = mStateStats.get(key); + if (stateStats == null) { + stateStats = createStateStats(key, timestampMs); + } + stateStats.increment(powerStats.stateStats.valueAt(i), timestampMs); + } mDeviceStats.increment(powerStats.stats, timestampMs); for (int i = powerStats.uidStats.size() - 1; i >= 0; i--) { int uid = powerStats.uidStats.keyAt(i); PowerComponentAggregatedPowerStats.UidStats uidStats = getUidStats(uid); if (uidStats.stats == null) { - createUidStats(uidStats); + createUidStats(uidStats, timestampMs); } uidStats.stats.increment(powerStats.uidStats.valueAt(i), timestampMs); } @@ -168,6 +205,7 @@ class PowerComponentAggregatedPowerStats { mStatsFactory = null; mUidStatsFactory = null; mDeviceStats = null; + mStateStats.clear(); for (int i = mUidStats.size() - 1; i >= 0; i--) { mUidStats.valueAt(i).stats = null; } @@ -178,6 +216,13 @@ class PowerComponentAggregatedPowerStats { if (uidStats == null) { uidStats = new UidStats(); uidStats.states = new int[mUidStateConfig.length]; + for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) { + if (mUidStateConfig[stateId].isTracked() + && stateId < mDeviceStateConfig.length + && mDeviceStateConfig[stateId].isTracked()) { + uidStats.states[stateId] = mDeviceStates[stateId]; + } + } mUidStats.put(uid, uidStats); } return uidStats; @@ -204,6 +249,26 @@ class PowerComponentAggregatedPowerStats { return false; } + boolean getStateStats(long[] outValues, int key, int[] deviceStates) { + if (deviceStates.length != mDeviceStateConfig.length) { + throw new IllegalArgumentException( + "Invalid number of tracked states: " + deviceStates.length + + " expected: " + mDeviceStateConfig.length); + } + MultiStateStats stateStats = mStateStats.get(key); + if (stateStats != null) { + stateStats.getStats(outValues, deviceStates); + return true; + } + return false; + } + + void forEachStateStatsKey(IntConsumer consumer) { + for (int i = mStateStats.size() - 1; i >= 0; i--) { + consumer.accept(mStateStats.keyAt(i)); + } + } + boolean getUidStats(long[] outValues, int uid, int[] uidStates) { if (uidStates.length != mUidStateConfig.length) { throw new IllegalArgumentException( @@ -218,7 +283,7 @@ class PowerComponentAggregatedPowerStats { return false; } - private void createDeviceStats() { + private void createDeviceStats(long timestampMs) { if (mStatsFactory == null) { if (mPowerStatsDescriptor == null) { return; @@ -229,13 +294,39 @@ class PowerComponentAggregatedPowerStats { mDeviceStats = mStatsFactory.create(); if (mPowerStatsTimestamp != UNKNOWN) { + timestampMs = mPowerStatsTimestamp; + } + if (timestampMs != UNKNOWN) { for (int stateId = 0; stateId < mDeviceStateConfig.length; stateId++) { - mDeviceStats.setState(stateId, mDeviceStates[stateId], mPowerStatsTimestamp); + int state = mDeviceStates[stateId]; + mDeviceStats.setState(stateId, state, timestampMs); + for (int i = mStateStats.size() - 1; i >= 0; i--) { + MultiStateStats stateStats = mStateStats.valueAt(i); + stateStats.setState(stateId, state, timestampMs); + } + } + } + } + + private MultiStateStats createStateStats(int key, long timestampMs) { + if (mStateStatsFactory == null) { + if (mPowerStatsDescriptor == null) { + return null; } + mStateStatsFactory = new MultiStateStats.Factory( + mPowerStatsDescriptor.stateStatsArrayLength, mDeviceStateConfig); } + + MultiStateStats stateStats = mStateStatsFactory.create(); + mStateStats.put(key, stateStats); + if (mDeviceStats != null) { + stateStats.copyStatesFrom(mDeviceStats); + } + + return stateStats; } - private void createUidStats(UidStats uidStats) { + private void createUidStats(UidStats uidStats, long timestampMs) { if (mUidStatsFactory == null) { if (mPowerStatsDescriptor == null) { return; @@ -245,9 +336,13 @@ class PowerComponentAggregatedPowerStats { } uidStats.stats = mUidStatsFactory.create(); - for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) { - if (mPowerStatsTimestamp != UNKNOWN) { - uidStats.stats.setState(stateId, uidStats.states[stateId], mPowerStatsTimestamp); + + if (mPowerStatsTimestamp != UNKNOWN) { + timestampMs = mPowerStatsTimestamp; + } + if (timestampMs != UNKNOWN) { + for (int stateId = 0; stateId < mUidStateConfig.length; stateId++) { + uidStats.stats.setState(stateId, uidStats.states[stateId], timestampMs); } } } @@ -268,6 +363,13 @@ class PowerComponentAggregatedPowerStats { serializer.endTag(null, XML_TAG_DEVICE_STATS); } + for (int i = 0; i < mStateStats.size(); i++) { + serializer.startTag(null, XML_TAG_STATE_STATS); + serializer.attributeInt(null, XML_ATTR_KEY, mStateStats.keyAt(i)); + mStateStats.valueAt(i).writeXml(serializer); + serializer.endTag(null, XML_TAG_STATE_STATS); + } + for (int i = mUidStats.size() - 1; i >= 0; i--) { int uid = mUidStats.keyAt(i); UidStats uidStats = mUidStats.valueAt(i); @@ -285,8 +387,10 @@ class PowerComponentAggregatedPowerStats { public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException { + String outerTag = parser.getName(); int eventType = parser.getEventType(); - while (eventType != XmlPullParser.END_DOCUMENT) { + while (eventType != XmlPullParser.END_DOCUMENT + && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) { if (eventType == XmlPullParser.START_TAG) { switch (parser.getName()) { case PowerStats.Descriptor.XML_TAG_DESCRIPTOR: @@ -297,17 +401,27 @@ class PowerComponentAggregatedPowerStats { break; case XML_TAG_DEVICE_STATS: if (mDeviceStats == null) { - createDeviceStats(); + createDeviceStats(UNKNOWN); } if (!mDeviceStats.readFromXml(parser)) { return false; } break; + case XML_TAG_STATE_STATS: + int key = parser.getAttributeInt(null, XML_ATTR_KEY); + MultiStateStats stats = mStateStats.get(key); + if (stats == null) { + stats = createStateStats(key, UNKNOWN); + } + if (!stats.readFromXml(parser)) { + return false; + } + break; case XML_TAG_UID_STATS: int uid = parser.getAttributeInt(null, XML_ATTR_UID); UidStats uidStats = getUidStats(uid); if (uidStats.stats == null) { - createUidStats(uidStats); + createUidStats(uidStats, UNKNOWN); } if (!uidStats.stats.readFromXml(parser)) { return false; @@ -328,6 +442,21 @@ class PowerComponentAggregatedPowerStats { mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats)); ipw.decreaseIndent(); } + + if (mStateStats.size() != 0) { + ipw.increaseIndent(); + ipw.println(mPowerStatsDescriptor.name + " states"); + ipw.increaseIndent(); + for (int i = 0; i < mStateStats.size(); i++) { + int key = mStateStats.keyAt(i); + MultiStateStats stateStats = mStateStats.valueAt(i); + stateStats.dump(ipw, stats -> + mConfig.getProcessor().stateStatsToString(mPowerStatsDescriptor, key, + stats)); + } + ipw.decreaseIndent(); + ipw.decreaseIndent(); + } } void dumpUid(IndentingPrintWriter ipw, int uid) { @@ -340,4 +469,29 @@ class PowerComponentAggregatedPowerStats { ipw.decreaseIndent(); } } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + IndentingPrintWriter ipw = new IndentingPrintWriter(sw); + ipw.increaseIndent(); + dumpDevice(ipw); + ipw.decreaseIndent(); + + int[] uids = new int[mUidStats.size()]; + for (int i = uids.length - 1; i >= 0; i--) { + uids[i] = mUidStats.keyAt(i); + } + Arrays.sort(uids); + for (int uid : uids) { + ipw.println(UserHandle.formatUid(uid)); + ipw.increaseIndent(); + dumpUid(ipw, uid); + ipw.decreaseIndent(); + } + + ipw.flush(); + + return sw.toString(); + } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java index ba4c127ac3d0..6a4c1f0406a9 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java @@ -32,7 +32,7 @@ public class PowerStatsAggregator { private static final long UNINITIALIZED = -1; private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; private final BatteryStatsHistory mHistory; - private final SparseArray<AggregatedPowerStatsProcessor> mProcessors = new SparseArray<>(); + private final SparseArray<PowerStatsProcessor> mProcessors = new SparseArray<>(); private AggregatedPowerStats mStats; private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; @@ -43,7 +43,7 @@ public class PowerStatsAggregator { mHistory = history; for (AggregatedPowerStatsConfig.PowerComponent powerComponentsConfig : aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs()) { - AggregatedPowerStatsProcessor processor = powerComponentsConfig.getProcessor(); + PowerStatsProcessor processor = powerComponentsConfig.getProcessor(); mProcessors.put(powerComponentsConfig.getPowerComponentId(), processor); } } 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 c76797bad66d..5dd11db2a2fc 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -17,9 +17,12 @@ package com.android.server.power.stats; import android.annotation.Nullable; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; import android.os.ConditionVariable; import android.os.Handler; -import android.os.PersistableBundle; +import android.power.PowerStatsInternal; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -30,7 +33,12 @@ import com.android.internal.os.PowerStats; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; /** @@ -43,6 +51,7 @@ import java.util.function.Consumer; public abstract class PowerStatsCollector { private static final String TAG = "PowerStatsCollector"; private static final int MILLIVOLTS_PER_VOLT = 1000; + private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000; private final Handler mHandler; protected final Clock mClock; private final long mThrottlePeriodMs; @@ -50,200 +59,6 @@ public abstract class PowerStatsCollector { private boolean mEnabled; private long mLastScheduledUpdateMs = -1; - /** - * Captures the positions and lengths of sections of the stats array, such as usage duration, - * power usage estimates etc. - */ - public static class StatsArrayLayout { - private static final String EXTRA_DEVICE_POWER_POSITION = "dp"; - private static final String EXTRA_DEVICE_DURATION_POSITION = "dd"; - private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de"; - private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; - private static final String EXTRA_UID_POWER_POSITION = "up"; - - protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; - - private int mDeviceStatsArrayLength; - private int mUidStatsArrayLength; - - protected int mDeviceDurationPosition; - private int mDeviceEnergyConsumerPosition; - private int mDeviceEnergyConsumerCount; - private int mDevicePowerEstimatePosition; - private int mUidPowerEstimatePosition; - - public int getDeviceStatsArrayLength() { - return mDeviceStatsArrayLength; - } - - public int getUidStatsArrayLength() { - return mUidStatsArrayLength; - } - - protected int addDeviceSection(int length) { - int position = mDeviceStatsArrayLength; - mDeviceStatsArrayLength += length; - return position; - } - - protected int addUidSection(int length) { - int position = mUidStatsArrayLength; - mUidStatsArrayLength += length; - return position; - } - - /** - * Declare that the stats array has a section capturing usage duration - */ - public void addDeviceSectionUsageDuration() { - mDeviceDurationPosition = addDeviceSection(1); - } - - /** - * Saves the usage duration in the corresponding <code>stats</code> element. - */ - public void setUsageDuration(long[] stats, long value) { - stats[mDeviceDurationPosition] = value; - } - - /** - * Extracts the usage duration from the corresponding <code>stats</code> element. - */ - public long getUsageDuration(long[] stats) { - return stats[mDeviceDurationPosition]; - } - - /** - * Declares that the stats array has a section capturing EnergyConsumer data from - * PowerStatsService. - */ - public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { - mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount); - mDeviceEnergyConsumerCount = energyConsumerCount; - } - - public int getEnergyConsumerCount() { - return mDeviceEnergyConsumerCount; - } - - /** - * Saves the accumulated energy for the specified rail the corresponding - * <code>stats</code> element. - */ - public void setConsumedEnergy(long[] stats, int index, long energy) { - stats[mDeviceEnergyConsumerPosition + index] = energy; - } - - /** - * Extracts the EnergyConsumer data from a device stats array for the specified - * EnergyConsumer. - */ - public long getConsumedEnergy(long[] stats, int index) { - return stats[mDeviceEnergyConsumerPosition + index]; - } - - /** - * Declare that the stats array has a section capturing a power estimate - */ - public void addDeviceSectionPowerEstimate() { - mDevicePowerEstimatePosition = addDeviceSection(1); - } - - /** - * Converts the supplied mAh power estimate to a long and saves it in the corresponding - * element of <code>stats</code>. - */ - public void setDevicePowerEstimate(long[] stats, double power) { - stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); - } - - /** - * Extracts the power estimate from a device stats array and converts it to mAh. - */ - public double getDevicePowerEstimate(long[] stats) { - return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; - } - - /** - * Declare that the UID stats array has a section capturing a power estimate - */ - public void addUidSectionPowerEstimate() { - mUidPowerEstimatePosition = addUidSection(1); - } - - /** - * Converts the supplied mAh power estimate to a long and saves it in the corresponding - * element of <code>stats</code>. - */ - public void setUidPowerEstimate(long[] stats, double power) { - stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); - } - - /** - * Extracts the power estimate from a UID stats array and converts it to mAh. - */ - public double getUidPowerEstimate(long[] stats) { - return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; - } - - /** - * Copies the elements of the stats array layout into <code>extras</code> - */ - public void toExtras(PersistableBundle extras) { - extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition); - extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION, - mDeviceEnergyConsumerPosition); - extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT, - mDeviceEnergyConsumerCount); - extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); - extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); - } - - /** - * Retrieves elements of the stats array layout from <code>extras</code> - */ - public void fromExtras(PersistableBundle extras) { - mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION); - mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION); - mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT); - mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION); - mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION); - } - - protected void putIntArray(PersistableBundle extras, String key, int[] array) { - if (array == null) { - return; - } - - StringBuilder sb = new StringBuilder(); - for (int value : array) { - if (!sb.isEmpty()) { - sb.append(','); - } - sb.append(value); - } - extras.putString(key, sb.toString()); - } - - protected int[] getIntArray(PersistableBundle extras, String key) { - String string = extras.getString(key); - if (string == null) { - return null; - } - String[] values = string.trim().split(","); - int[] result = new int[values.length]; - for (int i = 0; i < values.length; i++) { - try { - result[i] = Integer.parseInt(values[i]); - } catch (NumberFormatException e) { - Slog.wtf(TAG, "Invalid CSV format: " + string); - return null; - } - } - return result; - } - } - @GuardedBy("this") @SuppressWarnings("unchecked") private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList(); @@ -389,9 +204,83 @@ public abstract class PowerStatsCollector { } /** Calculate charge consumption (in microcoulombs) from a given energy and voltage */ - protected long uJtoUc(long deltaEnergyUj, int avgVoltageMv) { + protected static long uJtoUc(long deltaEnergyUj, int avgVoltageMv) { // To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times // since the last snapshot. Round off to the nearest whole long. return (deltaEnergyUj * MILLIVOLTS_PER_VOLT + (avgVoltageMv / 2)) / avgVoltageMv; } + + interface ConsumedEnergyRetriever { + int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType); + + @Nullable + long[] getConsumedEnergyUws(int[] energyConsumerIds); + } + + static class ConsumedEnergyRetrieverImpl implements ConsumedEnergyRetriever { + private final PowerStatsInternal mPowerStatsInternal; + + ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal) { + mPowerStatsInternal = powerStatsInternal; + } + + @Override + public int[] getEnergyConsumerIds(int energyConsumerType) { + if (mPowerStatsInternal == null) { + return new int[0]; + } + + EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo(); + if (energyConsumerInfo == null) { + return new int[0]; + } + + List<EnergyConsumer> energyConsumers = new ArrayList<>(); + for (EnergyConsumer energyConsumer : energyConsumerInfo) { + if (energyConsumer.type == energyConsumerType) { + energyConsumers.add(energyConsumer); + } + } + if (energyConsumers.isEmpty()) { + return new int[0]; + } + + energyConsumers.sort(Comparator.comparing(c -> c.ordinal)); + + int[] ids = new int[energyConsumers.size()]; + for (int i = 0; i < ids.length; i++) { + ids[i] = energyConsumers.get(i).id; + } + return ids; + } + + @Override + public long[] getConsumedEnergyUws(int[] energyConsumerIds) { + CompletableFuture<EnergyConsumerResult[]> future = + mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds); + EnergyConsumerResult[] results = null; + try { + results = future.get( + POWER_STATS_ENERGY_CONSUMERS_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.e(TAG, "Could not obtain energy consumers from PowerStatsService", e); + } + + if (results == null) { + return null; + } + + long[] energy = new long[energyConsumerIds.length]; + for (int i = 0; i < energyConsumerIds.length; i++) { + int id = energyConsumerIds[i]; + for (EnergyConsumerResult result : results) { + if (result.id == id) { + energy[i] = result.energyUWs; + break; + } + } + } + return energy; + } + } } 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 4f4ddca6c3fc..f6b198a88fc2 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -139,7 +139,7 @@ public class PowerStatsExporter { return; } - PowerStatsCollector.StatsArrayLayout layout = new PowerStatsCollector.StatsArrayLayout(); + PowerStatsLayout layout = new PowerStatsLayout(); layout.fromExtras(descriptor.extras); long[] deviceStats = new long[descriptor.statsArrayLength]; @@ -164,9 +164,20 @@ public class PowerStatsExporter { deviceScope.addConsumedPower(powerComponentId, totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED); + if (layout.isUidPowerAttributionSupported()) { + populateUidBatteryConsumers(batteryUsageStatsBuilder, powerComponent, + powerComponentStats, layout); + } + } + + private static void populateUidBatteryConsumers( + BatteryUsageStats.Builder batteryUsageStatsBuilder, + AggregatedPowerStatsConfig.PowerComponent powerComponent, + PowerComponentAggregatedPowerStats powerComponentStats, + PowerStatsLayout layout) { + int powerComponentId = powerComponent.getPowerComponentId(); + PowerStats.Descriptor descriptor = powerComponentStats.getPowerStatsDescriptor(); long[] uidStats = new long[descriptor.uidStatsArrayLength]; - ArrayList<Integer> uids = new ArrayList<>(); - powerComponentStats.collectUids(uids); boolean breakDownByProcState = batteryUsageStatsBuilder.isProcessStateDataNeeded() @@ -177,6 +188,8 @@ public class PowerStatsExporter { double[] powerByProcState = new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1]; double powerAllApps = 0; + ArrayList<Integer> uids = new ArrayList<>(); + powerComponentStats.collectUids(uids); for (int uid : uids) { UidBatteryConsumer.Builder builder = batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid); diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java new file mode 100644 index 000000000000..aa96409e85e9 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.os.PersistableBundle; +import android.util.Slog; + +import com.android.internal.os.PowerStats; + +/** + * Captures the positions and lengths of sections of the stats array, such as usage duration, + * power usage estimates etc. + */ +public class PowerStatsLayout { + private static final String TAG = "PowerStatsLayout"; + private static final String EXTRA_DEVICE_POWER_POSITION = "dp"; + private static final String EXTRA_DEVICE_DURATION_POSITION = "dd"; + private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION = "de"; + private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; + private static final String EXTRA_UID_POWER_POSITION = "up"; + + protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; + protected static final int UNSUPPORTED = -1; + + private int mDeviceStatsArrayLength; + private int mStateStatsArrayLength; + private int mUidStatsArrayLength; + + protected int mDeviceDurationPosition = UNSUPPORTED; + private int mDeviceEnergyConsumerPosition; + private int mDeviceEnergyConsumerCount; + private int mDevicePowerEstimatePosition = UNSUPPORTED; + private int mUidPowerEstimatePosition = UNSUPPORTED; + + public PowerStatsLayout() { + } + + public PowerStatsLayout(PowerStats.Descriptor descriptor) { + fromExtras(descriptor.extras); + } + + public int getDeviceStatsArrayLength() { + return mDeviceStatsArrayLength; + } + + public int getStateStatsArrayLength() { + return mStateStatsArrayLength; + } + + public int getUidStatsArrayLength() { + return mUidStatsArrayLength; + } + + protected int addDeviceSection(int length) { + int position = mDeviceStatsArrayLength; + mDeviceStatsArrayLength += length; + return position; + } + + protected int addStateSection(int length) { + int position = mStateStatsArrayLength; + mStateStatsArrayLength += length; + return position; + } + + protected int addUidSection(int length) { + int position = mUidStatsArrayLength; + mUidStatsArrayLength += length; + return position; + } + + /** + * Declare that the stats array has a section capturing usage duration + */ + public void addDeviceSectionUsageDuration() { + mDeviceDurationPosition = addDeviceSection(1); + } + + /** + * Saves the usage duration in the corresponding <code>stats</code> element. + */ + public void setUsageDuration(long[] stats, long value) { + stats[mDeviceDurationPosition] = value; + } + + /** + * Extracts the usage duration from the corresponding <code>stats</code> element. + */ + public long getUsageDuration(long[] stats) { + return stats[mDeviceDurationPosition]; + } + + /** + * Declares that the stats array has a section capturing EnergyConsumer data from + * PowerStatsService. + */ + public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { + mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount); + mDeviceEnergyConsumerCount = energyConsumerCount; + } + + public int getEnergyConsumerCount() { + return mDeviceEnergyConsumerCount; + } + + /** + * Saves the accumulated energy for the specified rail the corresponding + * <code>stats</code> element. + */ + public void setConsumedEnergy(long[] stats, int index, long energy) { + stats[mDeviceEnergyConsumerPosition + index] = energy; + } + + /** + * Extracts the EnergyConsumer data from a device stats array for the specified + * EnergyConsumer. + */ + public long getConsumedEnergy(long[] stats, int index) { + return stats[mDeviceEnergyConsumerPosition + index]; + } + + /** + * Declare that the stats array has a section capturing a power estimate + */ + public void addDeviceSectionPowerEstimate() { + mDevicePowerEstimatePosition = addDeviceSection(1); + } + + /** + * Converts the supplied mAh power estimate to a long and saves it in the corresponding + * element of <code>stats</code>. + */ + public void setDevicePowerEstimate(long[] stats, double power) { + stats[mDevicePowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Extracts the power estimate from a device stats array and converts it to mAh. + */ + public double getDevicePowerEstimate(long[] stats) { + return stats[mDevicePowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; + } + + /** + * Declare that the UID stats array has a section capturing a power estimate + */ + public void addUidSectionPowerEstimate() { + mUidPowerEstimatePosition = addUidSection(1); + } + + /** + * Returns true if power for this component is attributed to UIDs (apps). + */ + public boolean isUidPowerAttributionSupported() { + return mUidPowerEstimatePosition != UNSUPPORTED; + } + + /** + * Converts the supplied mAh power estimate to a long and saves it in the corresponding + * element of <code>stats</code>. + */ + public void setUidPowerEstimate(long[] stats, double power) { + stats[mUidPowerEstimatePosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER); + } + + /** + * Extracts the power estimate from a UID stats array and converts it to mAh. + */ + public double getUidPowerEstimate(long[] stats) { + return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER; + } + + /** + * Copies the elements of the stats array layout into <code>extras</code> + */ + public void toExtras(PersistableBundle extras) { + extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition); + extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION, + mDeviceEnergyConsumerPosition); + extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT, + mDeviceEnergyConsumerCount); + extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); + extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); + } + + /** + * Retrieves elements of the stats array layout from <code>extras</code> + */ + public void fromExtras(PersistableBundle extras) { + mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION); + mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION); + mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT); + mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION); + mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION); + } + + protected void putIntArray(PersistableBundle extras, String key, int[] array) { + if (array == null) { + return; + } + + StringBuilder sb = new StringBuilder(); + for (int value : array) { + if (!sb.isEmpty()) { + sb.append(','); + } + sb.append(value); + } + extras.putString(key, sb.toString()); + } + + protected int[] getIntArray(PersistableBundle extras, String key) { + String string = extras.getString(key); + if (string == null) { + return null; + } + String[] values = string.trim().split(","); + int[] result = new int[values.length]; + for (int i = 0; i < values.length; i++) { + try { + result[i] = Integer.parseInt(values[i]); + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Invalid CSV format: " + string); + return null; + } + } + return result; + } +} diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java index 7feb9643fb8f..0d5c5422b45c 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java @@ -27,7 +27,7 @@ import java.util.Collections; import java.util.List; /* - * The power estimation algorithm used by AggregatedPowerStatsProcessor can roughly be + * The power estimation algorithm used by PowerStatsProcessor can roughly be * described like this: * * 1. Estimate power usage for each state combination (e.g. power-battery/screen-on) using @@ -39,8 +39,8 @@ import java.util.List; * 2. For each UID, compute the proportion of the combined estimates in each state * and attribute the corresponding portion of the total power estimate in that state to the UID. */ -abstract class AggregatedPowerStatsProcessor { - private static final String TAG = "AggregatedPowerStatsProcessor"; +abstract class PowerStatsProcessor { + private static final String TAG = "PowerStatsProcessor"; private static final int INDEX_DOES_NOT_EXIST = -1; private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0; @@ -49,6 +49,8 @@ abstract class AggregatedPowerStatsProcessor { abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats); + abstract String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats); + abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats); protected static class PowerEstimationPlan { diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 2a9325544833..c8bcc5128c3a 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -54,8 +54,10 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.ext.SdkExtensions; import android.provider.DeviceConfig; +import android.util.ArrayMap; import android.util.Log; import android.util.LongArrayQueue; +import android.util.Pair; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.SparseIntArray; @@ -173,6 +175,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba // Accessed on the handler thread only. private long mRelativeBootTime = calculateRelativeBootTime(); + private final ArrayMap<Integer, Pair<Context, BroadcastReceiver>> mUserBroadcastReceivers; + RollbackManagerServiceImpl(Context context) { mContext = context; // Note that we're calling onStart here because this object is only constructed on @@ -210,6 +214,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } }); + mUserBroadcastReceivers = new ArrayMap<>(); + UserManager userManager = mContext.getSystemService(UserManager.class); for (UserHandle user : userManager.getUserHandles(true)) { registerUserCallbacks(user); @@ -275,7 +281,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } }, enableRollbackTimedOutFilter, null, getHandler()); - IntentFilter userAddedIntentFilter = new IntentFilter(Intent.ACTION_USER_ADDED); + IntentFilter userIntentFilter = new IntentFilter(); + userIntentFilter.addAction(Intent.ACTION_USER_ADDED); + userIntentFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -287,9 +295,15 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba return; } registerUserCallbacks(UserHandle.of(newUserId)); + } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { + final int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (newUserId == -1) { + return; + } + unregisterUserCallbacks(UserHandle.of(newUserId)); } } - }, userAddedIntentFilter, null, getHandler()); + }, userIntentFilter, null, getHandler()); registerTimeChangeReceiver(); } @@ -335,7 +349,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); filter.addDataScheme("package"); - context.registerReceiver(new BroadcastReceiver() { + BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { assertInWorkerThread(); @@ -354,7 +368,21 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba onPackageFullyRemoved(packageName); } } - }, filter, null, getHandler()); + }; + context.registerReceiver(receiver, filter, null, getHandler()); + mUserBroadcastReceivers.put(user.getIdentifier(), new Pair(context, receiver)); + } + + @AnyThread + private void unregisterUserCallbacks(UserHandle user) { + Pair<Context, BroadcastReceiver> pair = mUserBroadcastReceivers.get(user.getIdentifier()); + if (pair == null || pair.first == null || pair.second == null) { + Slog.e(TAG, "No receiver found for the user" + user); + return; + } + + pair.first.unregisterReceiver(pair.second); + mUserBroadcastReceivers.remove(user.getIdentifier()); } @ExtThread diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index 96f045d7e258..8138168f609e 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -44,6 +44,8 @@ public final class HapticFeedbackVibrationProvider { VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION); private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + private static final VibrationAttributes COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST); private final VibratorInfo mVibratorInfo; private final boolean mHapticTextHandleEnabled; @@ -120,7 +122,6 @@ public final class HapticFeedbackVibrationProvider { return getKeyboardVibration(effectId); case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE: - case HapticFeedbackConstants.ENTRY_BUMP: case HapticFeedbackConstants.DRAG_CROSSING: return getVibration( effectId, @@ -131,6 +132,7 @@ public final class HapticFeedbackVibrationProvider { case HapticFeedbackConstants.EDGE_RELEASE: case HapticFeedbackConstants.CALENDAR_DATE: case HapticFeedbackConstants.CONFIRM: + case HapticFeedbackConstants.BIOMETRIC_CONFIRM: case HapticFeedbackConstants.GESTURE_START: case HapticFeedbackConstants.SCROLL_ITEM_FOCUS: case HapticFeedbackConstants.SCROLL_LIMIT: @@ -143,6 +145,7 @@ public final class HapticFeedbackVibrationProvider { return getVibration(effectId, VibrationEffect.EFFECT_HEAVY_CLICK); case HapticFeedbackConstants.REJECT: + case HapticFeedbackConstants.BIOMETRIC_REJECT: return getVibration(effectId, VibrationEffect.EFFECT_DOUBLE_CLICK); case HapticFeedbackConstants.SAFE_MODE_ENABLED: @@ -207,6 +210,10 @@ public final class HapticFeedbackVibrationProvider { case HapticFeedbackConstants.KEYBOARD_RELEASE: attrs = createKeyboardVibrationAttributes(fromIme); break; + case HapticFeedbackConstants.BIOMETRIC_CONFIRM: + case HapticFeedbackConstants.BIOMETRIC_REJECT: + attrs = COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES; + break; default: attrs = TOUCH_VIBRATION_ATTRIBUTES; } @@ -225,6 +232,23 @@ public final class HapticFeedbackVibrationProvider { return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build(); } + /** + * Returns true if given haptic feedback is restricted to system apps with permission + * {@code android.permission.VIBRATE_SYSTEM_CONSTANTS}. + * + * @param effectId the haptic feedback effect ID to check. + * @return true if the haptic feedback is restricted, false otherwise. + */ + public boolean isRestrictedHapticFeedback(int effectId) { + switch (effectId) { + case HapticFeedbackConstants.BIOMETRIC_CONFIRM: + case HapticFeedbackConstants.BIOMETRIC_REJECT: + return true; + default: + return false; + } + } + /** Dumps relevant state. */ public void dump(String prefix, PrintWriter pw) { pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled); diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 9e9025e35e60..8281ac1c9d28 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -439,6 +439,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.w(TAG, "performHapticFeedback; haptic vibration provider not ready."); return null; } + if (hapticVibrationProvider.isRestrictedHapticFeedback(constant) + && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) { + Slog.w(TAG, "performHapticFeedback; no permission for effect " + constant); + return null; + } VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant); if (effect == null) { Slog.w(TAG, "performHapticFeedback; vibration absent for effect " + constant); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 0412fcb5416c..f6e0168fd236 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -493,6 +493,7 @@ public class WallpaperCropper { } } final Rect cropHint; + final SparseArray<Rect> defaultCrops; // A wallpaper with cropHints = Map.of(ORIENTATION_UNKNOWN, rect) is treated like // a wallpaper with cropHints = null and cropHint = rect. @@ -504,7 +505,7 @@ public class WallpaperCropper { if (multiCrop() && wallpaper.mCropHints.size() > 0) { // Some suggested crops per screen orientation were provided, // use them to compute the default crops for this device - SparseArray<Rect> defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize); + defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize); // Adapt the provided crops to match the actual crops for the default display SparseArray<Rect> updatedCropHints = new SparseArray<>(); for (int i = 0; i < wallpaper.mCropHints.size(); i++) { @@ -535,7 +536,7 @@ public class WallpaperCropper { wallpaper.cropHint.set(bitmapRect); } Point cropSize = new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height()); - SparseArray<Rect> defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize); + defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize); cropHint = getTotalCrop(defaultCrops); cropHint.offset(wallpaper.cropHint.left, wallpaper.cropHint.top); wallpaper.cropHint.set(cropHint); @@ -544,6 +545,7 @@ public class WallpaperCropper { } } else { cropHint = new Rect(wallpaper.cropHint); + defaultCrops = null; } if (DEBUG) { @@ -584,6 +586,33 @@ public class WallpaperCropper { || cropHint.height() > GLHelper.getMaxTextureSize() || cropHint.width() > GLHelper.getMaxTextureSize(); + float sampleSize = Float.MAX_VALUE; + if (multiCrop()) { + // If all crops for all orientations have more width and height in pixel + // than the display for this orientation, downsample the image + for (int i = 0; i < defaultCrops.size(); i++) { + int orientation = defaultCrops.keyAt(i); + Rect crop = defaultCrops.valueAt(i); + Point displayForThisOrientation = mWallpaperDisplayHelper + .getDefaultDisplaySizes().get(orientation); + if (displayForThisOrientation == null) continue; + float sampleSizeForThisOrientation = Math.max(1f, Math.min( + crop.width() / displayForThisOrientation.x, + crop.height() / displayForThisOrientation.y)); + sampleSize = Math.min(sampleSize, sampleSizeForThisOrientation); + } + // If the total crop has more width or height than either the max texture size + // or twice the largest display dimension, downsample the image + int maxCropSize = Math.min( + 2 * mWallpaperDisplayHelper.getDefaultDisplayLargestDimension(), + GLHelper.getMaxTextureSize()); + float minimumSampleSize = Math.max(1f, Math.max( + (float) cropHint.height() / maxCropSize, + (float) cropHint.width()) / maxCropSize); + sampleSize = Math.max(sampleSize, minimumSampleSize); + needScale = sampleSize > 1f; + } + //make sure screen aspect ratio is preserved if width is scaled under screen size if (needScale && !multiCrop()) { final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height(); @@ -598,7 +627,8 @@ public class WallpaperCropper { if (DEBUG_CROP) { Slog.v(TAG, "crop: w=" + cropHint.width() + " h=" + cropHint.height()); - Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight); + if (multiCrop()) Slog.v(TAG, "defaultCrops: " + defaultCrops); + if (!multiCrop()) Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight); Slog.v(TAG, "meas: w=" + options.outWidth + " h=" + options.outHeight); Slog.v(TAG, "crop?=" + needCrop + " scale?=" + needScale); } @@ -641,28 +671,17 @@ public class WallpaperCropper { options.inJustDecodeBounds = false; final Rect estimateCrop = new Rect(cropHint); - estimateCrop.scale(1f / options.inSampleSize); + if (!multiCrop()) estimateCrop.scale(1f / options.inSampleSize); + else estimateCrop.scale(1f / sampleSize); float hRatio = (float) wpData.mHeight / estimateCrop.height(); - if (multiCrop()) { - // make sure the crop height is at most the display largest dimension - hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension() - / estimateCrop.height(); - hRatio = Math.min(hRatio, 1f); - } final int destHeight = (int) (estimateCrop.height() * hRatio); final int destWidth = (int) (estimateCrop.width() * hRatio); // We estimated an invalid crop, try to adjust the cropHint to get a valid one. - if (destWidth > GLHelper.getMaxTextureSize()) { + if (!multiCrop() && destWidth > GLHelper.getMaxTextureSize()) { if (DEBUG) { Slog.w(TAG, "Invalid crop dimensions, trying to adjust."); } - if (multiCrop()) { - // clear custom crop guidelines, fallback to system default - wallpaper.mCropHints.clear(); - generateCropInternal(wallpaper); - return; - } int newHeight = (int) (wpData.mHeight / hRatio); int newWidth = (int) (wpData.mWidth / hRatio); @@ -679,16 +698,27 @@ public class WallpaperCropper { // We've got the safe cropHint; now we want to scale it properly to // the desired rectangle. // That's a height-biased operation: make it fit the hinted height. - final int safeHeight = (int) (estimateCrop.height() * hRatio + 0.5f); - final int safeWidth = (int) (estimateCrop.width() * hRatio + 0.5f); + final int safeHeight = !multiCrop() + ? (int) (estimateCrop.height() * hRatio + 0.5f) + : (int) (cropHint.height() / sampleSize + 0.5f); + final int safeWidth = !multiCrop() + ? (int) (estimateCrop.width() * hRatio + 0.5f) + : (int) (cropHint.width() / sampleSize + 0.5f); if (DEBUG_CROP) { Slog.v(TAG, "Decode parameters:"); - Slog.v(TAG, " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop); - Slog.v(TAG, " down sampling=" + options.inSampleSize - + ", hRatio=" + hRatio); - Slog.v(TAG, " dest=" + destWidth + "x" + destHeight); - Slog.v(TAG, " safe=" + safeWidth + "x" + safeHeight); + if (!multiCrop()) { + Slog.v(TAG, + " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop); + Slog.v(TAG, " down sampling=" + options.inSampleSize + + ", hRatio=" + hRatio); + Slog.v(TAG, " dest=" + destWidth + "x" + destHeight); + } + if (multiCrop()) { + Slog.v(TAG, " cropHint=" + cropHint); + Slog.v(TAG, " sampleSize=" + sampleSize); + } + Slog.v(TAG, " targetSize=" + safeWidth + "x" + safeHeight); Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize()); } @@ -703,24 +733,28 @@ public class WallpaperCropper { final ImageDecoder.Source srcData = ImageDecoder.createSource(wallpaper.getWallpaperFile()); - final int sampleSize = scale; + final int finalScale = scale; + final int rescaledBitmapWidth = (int) (0.5f + bitmapSize.x / sampleSize); + final int rescaledBitmapHeight = (int) (0.5f + bitmapSize.y / sampleSize); Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> { - decoder.setTargetSampleSize(sampleSize); + if (!multiCrop()) decoder.setTargetSampleSize(finalScale); + if (multiCrop()) { + decoder.setTargetSize(rescaledBitmapWidth, rescaledBitmapHeight); + } decoder.setCrop(estimateCrop); }); record.delete(); - if (cropped == null) { + if (!multiCrop() && cropped == null) { Slog.e(TAG, "Could not decode new wallpaper"); } else { // We are safe to create final crop with safe dimensions now. - final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped, - safeWidth, safeHeight, true); + final Bitmap finalCrop = multiCrop() ? cropped + : Bitmap.createScaledBitmap(cropped, safeWidth, safeHeight, true); if (multiCrop()) { - wallpaper.mSampleSize = - ((float) cropHint.height()) / finalCrop.getHeight(); + wallpaper.mSampleSize = sampleSize; } if (DEBUG) { @@ -739,9 +773,7 @@ public class WallpaperCropper { success = true; } } catch (Exception e) { - if (DEBUG) { - Slog.e(TAG, "Error decoding crop", e); - } + Slog.e(TAG, "Error decoding crop", e); } finally { IoUtils.closeQuietly(bos); IoUtils.closeQuietly(f); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 9a5961af6083..f1ba755836b2 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2269,6 +2269,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Point croppedBitmapSize = new Point( (int) (0.5f + wallpaper.cropHint.width() / wallpaper.mSampleSize), (int) (0.5f + wallpaper.cropHint.height() / wallpaper.mSampleSize)); + if (croppedBitmapSize.equals(0, 0)) { + // There is an ImageWallpaper, but there are no crop hints and the bitmap size is + // unknown (e.g. the default wallpaper). Return a special "null" value that will be + // handled by WallpaperManager, which will fetch the dimensions of the wallpaper. + return null; + } SparseArray<Rect> relativeDefaultCrops = mWallpaperCropper.getDefaultCrops(relativeSuggestedCrops, croppedBitmapSize); SparseArray<Rect> adjustedRelativeSuggestedCrops = new SparseArray<>(); diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java index eb170b702eb9..36e52008f223 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java @@ -17,6 +17,9 @@ package com.android.server.wearable; import static android.service.wearable.WearableSensingService.HOTWORD_AUDIO_STREAM_BUNDLE_KEY; +import static android.system.OsConstants.F_GETFL; +import static android.system.OsConstants.O_ACCMODE; +import static android.system.OsConstants.O_RDONLY; import android.Manifest; import android.annotation.NonNull; @@ -42,6 +45,8 @@ import android.os.SharedMemory; import android.service.voice.HotwordAudioStream; import android.service.voice.VoiceInteractionManagerInternal; import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback; +import android.system.ErrnoException; +import android.system.Os; import android.system.OsConstants; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -276,7 +281,10 @@ final class WearableSensingManagerPerUserService extends ParcelFileDescriptor parcelFileDescriptor, @Nullable IWearableSensingCallback wearableSensingCallback, RemoteCallback statusCallback) { - Slog.i(TAG, "onProvideDataStream in per user service."); + Slog.i( + TAG, + "onProvideDataStream in per user service. Is data stream read-only? " + + isReadOnly(parcelFileDescriptor)); synchronized (mLock) { if (!setUpServiceIfNeeded()) { Slog.w(TAG, "Detection service is not available at this moment."); @@ -505,10 +513,53 @@ final class WearableSensingManagerPerUserService extends String filename, AndroidFuture<ParcelFileDescriptor> futureFromWearableSensingService) throws RemoteException { - // TODO(b/331395522): Intercept the PFD received from the app process and verify it - // is read-only - callbackFromAppProcess.openFile(filename, futureFromWearableSensingService); + AndroidFuture<ParcelFileDescriptor> futureFromSystemServer = + new AndroidFuture<ParcelFileDescriptor>() + .whenComplete( + (pfdFromApp, throwable) -> { + if (throwable != null) { + Slog.e( + TAG, + "Error when reading file " + filename, + throwable); + futureFromWearableSensingService.complete(null); + return; + } + if (pfdFromApp == null) { + futureFromWearableSensingService.complete(null); + return; + } + if (isReadOnly(pfdFromApp)) { + futureFromWearableSensingService.complete( + pfdFromApp); + } else { + Slog.w( + TAG, + "Received writable ParcelFileDescriptor" + + " from app process. To prevent" + + " arbitrary data egress, sending null" + + " to WearableSensingService" + + " instead."); + futureFromWearableSensingService.complete(null); + } + }); + callbackFromAppProcess.openFile(filename, futureFromSystemServer); } }; } + + private static boolean isReadOnly(ParcelFileDescriptor parcelFileDescriptor) { + try { + int readMode = + Os.fcntlInt(parcelFileDescriptor.getFileDescriptor(), F_GETFL, 0) & O_ACCMODE; + return readMode == O_RDONLY; + } catch (ErrnoException ex) { + Slog.w( + TAG, + "Error encountered when trying to determine if the parcelFileDescriptor is" + + " read-only. Treating it as not read-only", + ex); + } + return false; + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 207d42b6b58d..88f86cc7cecb 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8544,7 +8544,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds // are already calculated in resolveFixedOrientationConfiguration. - } else if (!isLetterboxedForFixedOrientationAndAspectRatio()) { + // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. + } else if (!isLetterboxedForFixedOrientationAndAspectRatio() + && !mLetterboxUiController.hasFullscreenOverride()) { resolveAspectRatioRestriction(newParentConfiguration); } @@ -9060,8 +9062,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // vertically centered within parent bounds with insets, so position vertical bounds // within parent bounds with insets to prevent insets from unnecessarily trimming // vertical bounds. - final int bottom = Math.min(parentBoundsWithInsets.top + parentBounds.width() - 1, - parentBoundsWithInsets.bottom); + final int bottom = Math.min(parentBoundsWithInsets.top + + parentBoundsWithInsets.width() - 1, parentBoundsWithInsets.bottom); containingBounds.set(parentBounds.left, parentBoundsWithInsets.top, parentBounds.right, bottom); containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top, @@ -9072,8 +9074,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // horizontally centered within parent bounds with insets, so position horizontal bounds // within parent bounds with insets to prevent insets from unnecessarily trimming // horizontal bounds. - final int right = Math.min(parentBoundsWithInsets.left + parentBounds.height(), - parentBoundsWithInsets.right); + final int right = Math.min(parentBoundsWithInsets.left + + parentBoundsWithInsets.height(), parentBoundsWithInsets.right); containingBounds.set(parentBoundsWithInsets.left, parentBounds.top, right, parentBounds.bottom); containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top, diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index f7b4a6748411..b9979adbafd0 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -174,27 +174,6 @@ class BackNavigationController { } } - // This is needed to bridge the old and new back behavior with recents. While in - // Overview with live tile enabled, the previous app is technically focused but we - // add an input consumer to capture all input that would otherwise go to the apps - // being controlled by the animation. This means that the window resolved is not - // the right window to consume back while in overview, so we need to route it to - // launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing - // compat callback in VRI only works when the window is focused. - // This symptom also happen while shell transition enabled, we can check that by - // isTransientLaunch to know whether the focus window is point to live tile. - final RecentsAnimationController recentsAnimationController = - wmService.getRecentsAnimationController(); - final ActivityRecord tmpAR = window.mActivityRecord; - if ((tmpAR != null && tmpAR.isActivityTypeHomeOrRecents() - && tmpAR.mTransitionController.isTransientLaunch(tmpAR)) - || (recentsAnimationController != null - && recentsAnimationController.shouldApplyInputConsumer(tmpAR))) { - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by " - + "recents. Overriding back callback to recents controller callback."); - return null; - } - if (!window.isDrawn()) { ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focused window didn't have a valid surface drawn."); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index a8cbc621d966..88587666e321 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -519,8 +519,17 @@ class InsetsSourceProvider { final SurfaceControl leash = mAdapter.mCapturedLeash; mControlTarget = target; updateVisibility(); + boolean initiallyVisible = mClientVisible; + if (mSource.getType() == WindowInsets.Type.ime()) { + // The IME cannot be initially visible, see ControlAdapter#startAnimation below. + // Also, the ImeInsetsSourceConsumer clears the client visibility upon losing control, + // but this won't have reached here yet by the time the new control is created. + // Note: The DisplayImeController needs the correct previous client's visibility, so we + // only override the initiallyVisible here. + initiallyVisible = false; + } mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash, - mClientVisible, surfacePosition, getInsetsHint()); + initiallyVisible, surfacePosition, getInsetsHint()); ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource Control %s for target %s", mControl, mControlTarget); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 3f245456d849..f220c9d06e14 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1084,6 +1084,10 @@ final class LetterboxUiController { || mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN); } + boolean hasFullscreenOverride() { + return isSystemOverrideToFullscreenEnabled() || shouldApplyUserFullscreenOverride(); + } + float getUserMinAspectRatio() { switch (mUserAspectRatio) { case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE: diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 74616acf0325..ed88b5a7c449 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -241,6 +241,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; +import android.util.IntArray; import android.util.MergedConfiguration; import android.util.Pair; import android.util.Slog; @@ -1106,6 +1107,14 @@ public class WindowManagerService extends IWindowManager.Stub @GuardedBy("mGlobalLock") final SensitiveContentPackages mSensitiveContentPackages = new SensitiveContentPackages(); + /** + * UIDs for which a Toast has been shown to indicate + * {@link LocalService#addBlockScreenCaptureForApps(ArraySet) screen capture blocking}. This is + * used to ensure we don't keep re-showing the Toast every time the window becomes visible. + * UIDs are removed when the app is removed from the block list. + */ + @GuardedBy("mGlobalLock") + private final IntArray mCaptureBlockedToastShownUids = new IntArray(); /** Listener to notify activity manager about app transitions. */ final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier @@ -8746,6 +8755,15 @@ public class WindowManagerService extends IWindowManager.Stub if (modified) { WindowManagerService.this.refreshScreenCaptureDisabled(); } + if (sensitiveContentImprovements()) { + for (int i = 0; i < packageInfos.size(); i++) { + int uid = packageInfos.valueAt(i).getUid(); + if (mCaptureBlockedToastShownUids.contains(uid)) { + mCaptureBlockedToastShownUids.remove( + mCaptureBlockedToastShownUids.indexOf(uid)); + } + } + } } } @@ -8756,6 +8774,9 @@ public class WindowManagerService extends IWindowManager.Stub if (modified) { WindowManagerService.this.refreshScreenCaptureDisabled(); } + if (sensitiveContentImprovements()) { + mCaptureBlockedToastShownUids.clear(); + } } } @@ -10157,9 +10178,13 @@ public class WindowManagerService extends IWindowManager.Stub * on sensitive content protections. */ private void showToastIfBlockingScreenCapture(@NonNull WindowState w) { - // TODO(b/323580163): Check if already shown and update shown state. - if (mSensitiveContentPackages.shouldBlockScreenCaptureForApp(w.getOwningPackage(), - w.getOwningUid(), w.getWindowToken())) { + int uid = w.getOwningUid(); + if (mCaptureBlockedToastShownUids.contains(uid)) { + return; + } + if (mSensitiveContentPackages.shouldBlockScreenCaptureForApp(w.getOwningPackage(), uid, + w.getWindowToken())) { + mCaptureBlockedToastShownUids.add(uid); mH.post(() -> { Toast.makeText(mContext, Looper.getMainLooper(), mContext.getString(R.string.screen_not_shared_sensitive_content), diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 14ec41f072dd..e7088832500f 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1367,10 +1367,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final IBinder callerActivityToken = operation.getActivityToken(); final Intent activityIntent = operation.getActivityIntent(); final Bundle activityOptions = operation.getBundle(); - final int result = mService.getActivityStartController() + final int result = waitAsyncStart(() -> mService.getActivityStartController() .startActivityInTaskFragment(taskFragment, activityIntent, activityOptions, callerActivityToken, caller.mUid, caller.mPid, - errorCallbackToken); + errorCallbackToken)); if (!isStartResultSuccessful(result)) { sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, opType, convertStartFailureToThrowable(result, activityIntent)); diff --git a/services/core/jni/com_android_server_am_OomConnection.cpp b/services/core/jni/com_android_server_am_OomConnection.cpp index 054937fc683e..4d07776d8023 100644 --- a/services/core/jni/com_android_server_am_OomConnection.cpp +++ b/services/core/jni/com_android_server_am_OomConnection.cpp @@ -92,9 +92,11 @@ static jobjectArray android_server_am_OomConnection_waitOom(JNIEnv* env, jobject memevent_listener.deregisterAllEvents(); jniThrowRuntimeException(env, "Failed creating java string for process name"); } - jobject java_oom_kill = env->NewObject(sOomKillRecordInfo.clazz, sOomKillRecordInfo.ctor, - oom_kill.timestamp_ms, oom_kill.pid, oom_kill.uid, - process_name, oom_kill.oom_score_adj); + jobject java_oom_kill = + env->NewObject(sOomKillRecordInfo.clazz, sOomKillRecordInfo.ctor, + oom_kill.timestamp_ms, oom_kill.pid, oom_kill.uid, process_name, + oom_kill.oom_score_adj, oom_kill.total_vm_kb, oom_kill.anon_rss_kb, + oom_kill.file_rss_kb, oom_kill.shmem_rss_kb, oom_kill.pgtables_kb); if (java_oom_kill == NULL) { memevent_listener.deregisterAllEvents(); jniThrowRuntimeException(env, "Failed to create OomKillRecord object"); @@ -115,8 +117,8 @@ int register_android_server_am_OomConnection(JNIEnv* env) { sOomKillRecordInfo.clazz = FindClassOrDie(env, "android/os/OomKillRecord"); sOomKillRecordInfo.clazz = MakeGlobalRefOrDie(env, sOomKillRecordInfo.clazz); - sOomKillRecordInfo.ctor = - GetMethodIDOrDie(env, sOomKillRecordInfo.clazz, "<init>", "(JIILjava/lang/String;S)V"); + sOomKillRecordInfo.ctor = GetMethodIDOrDie(env, sOomKillRecordInfo.clazz, "<init>", + "(JIILjava/lang/String;SJJJJJ)V"); return RegisterMethodsOrDie(env, "com/android/server/am/OomConnection", sOomConnectionMethods, NELEM(sOomConnectionMethods)); diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index f1962cb432aa..6143f1dd5b1c 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -176,7 +176,9 @@ <xs:element type="idleScreenRefreshRateTimeout" name="idleScreenRefreshRateTimeout" minOccurs="0"> <xs:annotation name="final"/> </xs:element> - + <xs:element name="supportsVrr" type="xs:boolean" minOccurs="0"> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> </xs:element> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 170434cff3ff..45ec8f250efa 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -152,6 +152,7 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampSlowIncreaseIdle(); method public final com.android.server.display.config.SensorDetails getScreenOffBrightnessSensor(); method public final com.android.server.display.config.IntegerArray getScreenOffBrightnessSensorValueToLux(); + method public final boolean getSupportsVrr(); method public final com.android.server.display.config.SensorDetails getTempSensor(); method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling(); method public final com.android.server.display.config.UsiVersion getUsiVersion(); @@ -188,6 +189,7 @@ package com.android.server.display.config { method public final void setScreenBrightnessRampSlowIncreaseIdle(java.math.BigDecimal); method public final void setScreenOffBrightnessSensor(com.android.server.display.config.SensorDetails); method public final void setScreenOffBrightnessSensorValueToLux(com.android.server.display.config.IntegerArray); + method public final void setSupportsVrr(boolean); method public final void setTempSensor(com.android.server.display.config.SensorDetails); method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling); method public final void setUsiVersion(com.android.server.display.config.UsiVersion); diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index f5ba50d7f079..bb46c44c56c4 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Context; import android.credentials.ClearCredentialStateException; import android.credentials.ClearCredentialStateRequest; +import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.IClearCredentialStateCallback; import android.credentials.selection.ProviderData; @@ -41,7 +42,7 @@ import java.util.Set; public final class ClearRequestSession extends RequestSession<ClearCredentialStateRequest, IClearCredentialStateCallback, Void> implements ProviderSession.ProviderInternalCallback<Void> { - private static final String TAG = "GetRequestSession"; + private static final String TAG = CredentialManager.TAG; public ClearRequestSession(Context context, RequestSession.SessionLifetime sessionCallback, Object lock, int userId, int callingUid, diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index cac42b17553a..3513cb54c9bd 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -50,7 +50,7 @@ import java.util.Set; public final class CreateRequestSession extends RequestSession<CreateCredentialRequest, ICreateCredentialCallback, CreateCredentialResponse> implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> { - private static final String TAG = "CreateRequestSession"; + private static final String TAG = CredentialManager.TAG; private final Set<ComponentName> mPrimaryProviders; CreateRequestSession(@NonNull Context context, RequestSession.SessionLifetime sessionCallback, diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 281fb1c4635b..6ef14366b9e7 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -34,6 +34,7 @@ import android.content.pm.PackageManager; import android.credentials.ClearCredentialStateRequest; import android.credentials.CreateCredentialException; import android.credentials.CreateCredentialRequest; +import android.credentials.CredentialManager; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; import android.credentials.GetCandidateCredentialsException; @@ -92,7 +93,7 @@ public final class CredentialManagerService extends AbstractMasterSystemService< CredentialManagerService, CredentialManagerServiceImpl> { - private static final String TAG = "CredManSysService"; + private static final String TAG = CredentialManager.TAG; private static final String PERMISSION_DENIED_ERROR = "permission_denied"; private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR = "Caller is missing WRITE_SECURE_SETTINGS permission"; diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java index 379800b31597..38ad5b6594b5 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.service.credentials.CredentialProviderInfoFactory; import android.util.Slog; @@ -36,7 +37,7 @@ import java.util.List; */ public final class CredentialManagerServiceImpl extends AbstractPerUserSystemService<CredentialManagerServiceImpl, CredentialManagerService> { - private static final String TAG = "CredManSysServiceImpl"; + private static final String TAG = CredentialManager.TAG; @GuardedBy("mLock") @NonNull @@ -93,7 +94,10 @@ public final class CredentialManagerServiceImpl extends public ProviderSession initiateProviderSessionForRequestLocked( RequestSession requestSession, List<String> requestOptions) { if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) { - Slog.i(TAG, "Service does not have the required capabilities"); + if (mInfo != null) { + Slog.i(TAG, "Service does not have the required capabilities: " + + mInfo.getComponentName()); + } return null; } if (mInfo == null) { diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index 24f66977ee90..bfa2d6138f04 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -48,7 +48,6 @@ import java.util.UUID; /** Initiates the Credential Manager UI and receives results. */ public class CredentialManagerUi { - private static final String TAG = "CredentialManagerUi"; @NonNull private final CredentialManagerUiCallback mCallbacks; @NonNull diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java index fd2a9a20640b..69d32a0f8825 100644 --- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.credentials.Constants; +import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.GetCandidateCredentialsException; import android.credentials.GetCandidateCredentialsResponse; @@ -54,7 +55,7 @@ import java.util.Set; public class GetCandidateRequestSession extends RequestSession<GetCredentialRequest, IGetCandidateCredentialsCallback, GetCandidateCredentialsResponse> implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> { - private static final String TAG = "GetCandidateRequestSession"; + private static final String TAG = CredentialManager.TAG; private static final String SESSION_ID_KEY = "autofill_session_id"; private static final String REQUEST_ID_KEY = "autofill_request_id"; diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index d55d8effd381..c26229b75300 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.credentials.CredentialManager; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; @@ -48,7 +49,7 @@ import java.util.Set; public class GetRequestSession extends RequestSession<GetCredentialRequest, IGetCredentialCallback, GetCredentialResponse> implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> { - private static final String TAG = "GetRequestSession"; + private static final String TAG = CredentialManager.TAG; public GetRequestSession(Context context, RequestSession.SessionLifetime sessionCallback, Object lock, int userId, int callingUid, diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 16bf17781eea..ac4aac694c3a 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -20,6 +20,7 @@ import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; +import android.credentials.CredentialManager; import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; @@ -44,7 +45,7 @@ import java.util.Map; public class MetricUtilities { private static final boolean LOG_FLAG = true; - private static final String TAG = "MetricUtilities"; + private static final String TAG = CredentialManager.TAG; public static final String USER_CANCELED_SUBSTRING = "TYPE_USER_CANCELED"; public static final int MIN_EMIT_WAIT_TIME_MS = 10; diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java index e4b5c776301e..f6b107b60d62 100644 --- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; +import android.credentials.CredentialManager; import android.credentials.CredentialOption; import android.credentials.GetCredentialRequest; import android.credentials.IGetCredentialCallback; @@ -44,7 +45,7 @@ import java.util.stream.Collectors; * responses from providers, and the UX app, and updates the provider(s) state. */ public class PrepareGetRequestSession extends GetRequestSession { - private static final String TAG = "PrepareGetRequestSession"; + private static final String TAG = CredentialManager.TAG; private final IPrepareGetCredentialCallback mPrepareGetCredentialCallback; diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index 6a1b1db756b5..6759dbb6f534 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; import android.credentials.ClearCredentialStateException; +import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.selection.ProviderData; import android.credentials.selection.ProviderPendingIntentResponse; @@ -37,7 +38,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS Void> implements RemoteCredentialService.ProviderCallbacks<Void> { - private static final String TAG = "ProviderClearSession"; + private static final String TAG = CredentialManager.TAG; private ClearCredentialStateException mProviderException; diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index 6361aeb4f0d7..bee7f6c9e908 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.credentials.CreateCredentialException; import android.credentials.CreateCredentialResponse; +import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.selection.CreateCredentialProviderData; import android.credentials.selection.Entry; @@ -51,7 +52,7 @@ import java.util.Map; */ public final class ProviderCreateSession extends ProviderSession< BeginCreateCredentialRequest, BeginCreateCredentialResponse> { - private static final String TAG = "ProviderCreateSession"; + private static final String TAG = CredentialManager.TAG; // Key to be used as an entry key for a save entry public static final String SAVE_ENTRY_KEY = "save_entry_key"; diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index c5f292118030..e18ef2b3230d 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -22,6 +22,7 @@ import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.credentials.CredentialManager; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; @@ -61,7 +62,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential BeginGetCredentialResponse> implements RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> { - private static final String TAG = "ProviderGetSession"; + private static final String TAG = CredentialManager.TAG; // Key to be used as the entry key for an action entry public static final String ACTION_ENTRY_KEY = "action_key"; // Key to be used as the entry key for the authentication entry diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index f162916690fd..83f9c24cdaf4 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -22,6 +22,7 @@ import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.credentials.CredentialManager; import android.credentials.CredentialOption; import android.credentials.GetCredentialException; import android.credentials.GetCredentialResponse; @@ -58,7 +59,7 @@ import java.util.stream.Stream; public class ProviderRegistryGetSession extends ProviderSession<CredentialOption, Set<CredentialDescriptionRegistry.FilterResult>> { - private static final String TAG = "ProviderRegistryGetSession"; + private static final String TAG = CredentialManager.TAG; @VisibleForTesting static final String CREDENTIAL_ENTRY_KEY = "credential_key"; diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index dfc08f04386e..8f0ae9058814 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.credentials.Credential; +import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.selection.ProviderData; import android.credentials.selection.ProviderPendingIntentResponse; @@ -44,7 +45,7 @@ import java.util.UUID; public abstract class ProviderSession<T, R> implements RemoteCredentialService.ProviderCallbacks<R> { - private static final String TAG = "ProviderSession"; + private static final String TAG = CredentialManager.TAG; @NonNull protected final Context mContext; diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java index 4bcf8be0d21e..c36140685e03 100644 --- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java +++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.credentials.ClearCredentialStateException; import android.credentials.CreateCredentialException; +import android.credentials.CredentialManager; import android.credentials.GetCredentialException; import android.os.Binder; import android.os.Handler; @@ -58,7 +59,7 @@ import java.util.concurrent.atomic.AtomicReference; */ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialProviderService> { - private static final String TAG = "RemoteCredentialService"; + private static final String TAG = CredentialManager.TAG; /** Timeout for a single request. */ private static final long TIMEOUT_REQUEST_MILLIS = 3 * DateUtils.SECOND_IN_MILLIS; /** Timeout to unbind after the task queue is empty. */ diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index a5b9aa68b22e..054ba2bca71b 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -23,6 +23,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.flags.Flags; import android.credentials.selection.ProviderData; @@ -56,7 +57,7 @@ import java.util.concurrent.ConcurrentHashMap; * every time a new response type is expected from the providers. */ abstract class RequestSession<T, U, V> implements CredentialManagerUi.CredentialManagerUiCallback { - private static final String TAG = "RequestSession"; + private static final String TAG = CredentialManager.TAG; public interface SessionLifetime { /** Called when the user makes a selection. */ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index f39d0193f28a..065c14e3f208 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -103,7 +103,7 @@ final class DevicePolicyEngine { UserManager.DISALLOW_CELLULAR_2G); //TODO(b/295504706) : Speak to security team to decide what to set Policy_Size_Limit - private static final int DEFAULT_POLICY_SIZE_LIMIT = -1; + static final int DEFAULT_POLICY_SIZE_LIMIT = -1; private final Context mContext; private final UserManager mUserManager; @@ -225,7 +225,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value, policyDefinition, userId)) { return; @@ -350,7 +350,7 @@ final class DevicePolicyEngine { } PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin); } @@ -496,7 +496,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value, policyDefinition, UserHandle.USER_ALL)) { return; @@ -568,7 +568,7 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition); - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { decreasePolicySizeForAdmin(policyState, enforcingAdmin); } @@ -1598,6 +1598,7 @@ final class DevicePolicyEngine { existingPolicySize = sizeOf(policyState.getPoliciesSetByAdmins().get(admin)); } int policySize = sizeOf(value); + // Policy size limit is disabled if mPolicySizeLimit is -1. if (mPolicySizeLimit == -1 || currentAdminPoliciesSize + policySize - existingPolicySize < mPolicySizeLimit) { @@ -1657,10 +1658,6 @@ final class DevicePolicyEngine { * the limitation. */ void setMaxPolicyStorageLimit(int storageLimit) { - if (storageLimit < DEFAULT_POLICY_SIZE_LIMIT && storageLimit != -1) { - throw new IllegalArgumentException("Can't set a size limit less than the minimum " - + "allowed size."); - } mPolicySizeLimit = storageLimit; } @@ -1672,6 +1669,15 @@ final class DevicePolicyEngine { return mPolicySizeLimit; } + int getPolicySizeForAdmin(EnforcingAdmin admin) { + if (mAdminPolicySize.contains(admin.getUserId()) + && mAdminPolicySize.get( + admin.getUserId()).containsKey(admin)) { + return mAdminPolicySize.get(admin.getUserId()).get(admin); + } + return 0; + } + public void dump(IndentingPrintWriter pw) { synchronized (mLock) { pw.println("Local Policies: "); @@ -1906,7 +1912,7 @@ final class DevicePolicyEngine { private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer) throws IOException { - if (Flags.devicePolicySizeTrackingInternalEnabled()) { + if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { if (mAdminPolicySize != null) { for (int i = 0; i < mAdminPolicySize.size(); i++) { int userId = mAdminPolicySize.keyAt(i); @@ -1930,7 +1936,7 @@ final class DevicePolicyEngine { private void writeMaxPolicySizeInner(TypedXmlSerializer serializer) throws IOException { - if (!Flags.devicePolicySizeTrackingInternalEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { return; } serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT); @@ -2095,7 +2101,7 @@ final class DevicePolicyEngine { private void readMaxPolicySizeInner(TypedXmlPullParser parser) throws XmlPullParserException, IOException { - if (!Flags.devicePolicySizeTrackingInternalEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { return; } mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 1dd719eb02aa..cb637579d8db 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -88,6 +88,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WINDOWS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT; import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.Manifest.permission.MASTER_CLEAR; import static android.Manifest.permission.NOTIFY_PENDING_SYSTEM_UPDATE; @@ -268,6 +269,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS; +import static com.android.server.devicepolicy.DevicePolicyEngine.DEFAULT_POLICY_SIZE_LIMIT; import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; @@ -742,7 +744,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * These cannot be set on the managed profile's parent DPM instance */ private static final int PROFILE_KEYGUARD_FEATURES_PROFILE_ONLY = - DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; + DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS + | DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL; /** Keyguard features that are allowed to be set on a managed profile */ private static final int PROFILE_KEYGUARD_FEATURES = @@ -2251,11 +2254,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (userHandle == UserHandle.USER_SYSTEM) { mStateCache.setDeviceProvisioned(policy.mUserSetupComplete); } + if (Flags.headlessSingleUserBadDeviceAdminStateFix()) { + fixBadDeviceAdminStateForInternalUsers(userHandle, policy); + } } return policy; } } + private void fixBadDeviceAdminStateForInternalUsers(int userId, DevicePolicyData policy) { + ComponentName component = mOwners.getDeviceOwnerComponent(); + int doUserId = mOwners.getDeviceOwnerUserId(); + ComponentName cloudDpc = new ComponentName( + "com.google.android.apps.work.clouddpc", + "com.google.android.apps.work.clouddpc.receivers.CloudDeviceAdminReceiver"); + if (component == null || doUserId != userId || !component.equals(cloudDpc)) { + return; + } + Slogf.i(LOG_TAG, "Attempting to apply a temp fix for cloudpc internal users' bad state."); + final int n = policy.mAdminList.size(); + for (int i = 0; i < n; i++) { + ActiveAdmin admin = policy.mAdminList.get(i); + if (component.equals(admin.info.getComponent())) { + Slogf.i(LOG_TAG, "An ActiveAdmin already exists, fix not required."); + return; + } + } + DeviceAdminInfo dai = findAdmin(component, userId, /* throwForMissingPermission= */ false); + if (dai != null) { + ActiveAdmin ap = new ActiveAdmin(dai, /* parent */ false); + policy.mAdminMap.put(ap.info.getComponent(), ap); + policy.mAdminList.add(ap); + Slogf.i(LOG_TAG, "Fix applied, an ActiveAdmin has been added."); + } + } + /** * Creates and loads the policy data from xml for data that is shared between * various profiles of a user. In contrast to {@link #getUserData(int)} @@ -12138,7 +12171,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (packageList != null) { - if (!Flags.devicePolicySizeTrackingInternalEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { for (String pkg : packageList) { PolicySizeVerifier.enforceMaxPackageNameLength(pkg); } @@ -13913,7 +13946,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (!Flags.devicePolicySizeTrackingInternalEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { PolicySizeVerifier.enforceMaxStringLength(accountType, "account type"); } @@ -14527,7 +14560,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages) throws SecurityException { Objects.requireNonNull(packages, "packages is null"); - if (!Flags.devicePolicySizeTrackingInternalEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { for (String pkg : packages) { PolicySizeVerifier.enforceMaxPackageNameLength(pkg); } @@ -24536,19 +24569,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) { - if (!Flags.devicePolicySizeTrackingInternalEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { return; } CallerIdentity caller = getCallerIdentity(callerPackageName); enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(), caller.getUserId()); + if (storageLimit < DEFAULT_POLICY_SIZE_LIMIT && storageLimit != -1) { + throw new IllegalArgumentException("Can't set a size limit less than the minimum " + + "allowed size."); + } mDevicePolicyEngine.setMaxPolicyStorageLimit(storageLimit); } @Override public int getMaxPolicyStorageLimit(String callerPackageName) { - if (!Flags.devicePolicySizeTrackingInternalEnabled()) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { return -1; } CallerIdentity caller = getCallerIdentity(callerPackageName); @@ -24559,6 +24596,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public void forceSetMaxPolicyStorageLimit(String callerPackageName, int storageLimit) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + return; + } + CallerIdentity caller = getCallerIdentity(callerPackageName); + enforcePermission(MANAGE_DEVICE_POLICY_STORAGE_LIMIT, caller.getPackageName(), + caller.getUserId()); + + mDevicePolicyEngine.setMaxPolicyStorageLimit(storageLimit); + } + + @Override + public int getPolicySizeForAdmin( + String callerPackageName, android.app.admin.EnforcingAdmin admin) { + if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { + return -1; + } + CallerIdentity caller = getCallerIdentity(callerPackageName); + enforcePermission(MANAGE_DEVICE_POLICY_STORAGE_LIMIT, caller.getPackageName(), + caller.getUserId()); + + return mDevicePolicyEngine.getPolicySizeForAdmin( + EnforcingAdmin.createEnforcingAdmin(admin)); + } + + @Override public int getHeadlessDeviceOwnerMode(String callerPackageName) { final CallerIdentity caller = getCallerIdentity(callerPackageName); enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(), diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java index d234dee3c8f7..02590f97ab6b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.admin.Authority; import android.app.admin.DeviceAdminAuthority; import android.app.admin.DpcAuthority; +import android.app.admin.PackagePermissionPolicyKey; import android.app.admin.RoleAuthority; import android.app.admin.UnknownAuthority; import android.content.ComponentName; @@ -105,6 +106,32 @@ final class EnforcingAdmin { userId, activeAdmin); } + static EnforcingAdmin createEnforcingAdmin(android.app.admin.EnforcingAdmin admin) { + Objects.requireNonNull(admin); + Authority authority = admin.getAuthority(); + Set<String> internalAuthorities = new HashSet<>(); + if (DpcAuthority.DPC_AUTHORITY.equals(authority)) { + return new EnforcingAdmin( + admin.getPackageName(), admin.getComponentName(), + Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier(), + /* activeAdmin = */ null); + } else if (DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY.equals(authority)) { + return new EnforcingAdmin( + admin.getPackageName(), admin.getComponentName(), + Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(), + /* activeAdmin = */ null); + } else if (authority instanceof RoleAuthority roleAuthority) { + return new EnforcingAdmin( + admin.getPackageName(), admin.getComponentName(), + Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(), + /* activeAdmin = */ null, + /* isRoleAuthority = */ true); + } + return new EnforcingAdmin(admin.getPackageName(), admin.getComponentName(), + Set.of(), admin.getUserHandle().getIdentifier(), + /* activeAdmin = */ null); + } + static String getRoleAuthorityOf(String roleName) { return ROLE_AUTHORITY_PREFIX + roleName; } @@ -154,6 +181,20 @@ final class EnforcingAdmin { mActiveAdmin = activeAdmin; } + private EnforcingAdmin( + String packageName, @Nullable ComponentName componentName, Set<String> authorities, + int userId, @Nullable ActiveAdmin activeAdmin, boolean isRoleAuthority) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(authorities); + + mIsRoleAuthority = isRoleAuthority; + mPackageName = packageName; + mComponentName = componentName; + mAuthorities = new HashSet<>(authorities); + mUserId = userId; + mActiveAdmin = activeAdmin; + } + private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) { Set<String> roles = getRoles(packageName, userId); Set<String> authorities = new HashSet<>(); diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java index 94ee0a871448..a547d0f94ea3 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -17,9 +17,7 @@ package com.android.server.backup; import static android.Manifest.permission.BACKUP; -import static android.Manifest.permission.DUMP; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; -import static android.Manifest.permission.PACKAGE_USAGE_STATS; import static com.android.server.backup.testing.TransportData.backupTransport; @@ -73,10 +71,7 @@ import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowContextWrapper; import java.io.File; -import java.io.FileDescriptor; import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; /** Tests for {@link BackupManagerService}. */ @RunWith(RobolectricTestRunner.class) @@ -1461,67 +1456,12 @@ public class BackupManagerServiceRoboTest { // Service tests // --------------------------------------------- - /** Test that the backup service routes methods correctly to the user that requests it. */ - @Test - public void testDump_onRegisteredUser_callsMethodForUser() throws Exception { - grantDumpPermissions(); - BackupManagerService backupManagerService = createSystemRegisteredService(); - File testFile = createTestFile(); - FileDescriptor fileDescriptor = new FileDescriptor(); - PrintWriter printWriter = new PrintWriter(testFile); - String[] args = {"1", "2"}; - ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM)); - - backupManagerService.dump(fileDescriptor, printWriter, args); - - verify(mUserSystemService).dump(fileDescriptor, printWriter, args); - } - - /** Test that the backup service does not route methods for non-registered users. */ - @Test - public void testDump_onUnknownUser_doesNotPropagateCall() throws Exception { - grantDumpPermissions(); - BackupManagerService backupManagerService = createService(); - File testFile = createTestFile(); - FileDescriptor fileDescriptor = new FileDescriptor(); - PrintWriter printWriter = new PrintWriter(testFile); - String[] args = {"1", "2"}; - - backupManagerService.dump(fileDescriptor, printWriter, args); - - verify(mUserOneService, never()).dump(fileDescriptor, printWriter, args); - } - - /** Test that 'dumpsys backup users' dumps the list of users registered in backup service*/ - @Test - public void testDump_users_dumpsListOfRegisteredUsers() { - grantDumpPermissions(); - BackupManagerService backupManagerService = createSystemRegisteredService(); - registerUser(backupManagerService, mUserOneId, mUserOneService); - StringWriter out = new StringWriter(); - PrintWriter writer = new PrintWriter(out); - String[] args = {"users"}; - - backupManagerService.dump(null, writer, args); - - writer.flush(); - assertEquals( - String.format("%s %d %d\n", BackupManagerService.DUMP_RUNNING_USERS_MESSAGE, - UserHandle.USER_SYSTEM, mUserOneId), - out.toString()); - } - private File createTestFile() throws IOException { File testFile = new File(mContext.getFilesDir(), "test"); testFile.createNewFile(); return testFile; } - private void grantDumpPermissions() { - mShadowContext.grantPermissions(DUMP); - mShadowContext.grantPermissions(PACKAGE_USAGE_STATS); - } - /** * Test that the backup services throws a {@link SecurityException} if the caller does not have * INTERACT_ACROSS_USERS_FULL permission and passes a different user id. diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 66dd43a19859..b0eee0881f63 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -133,8 +133,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; +import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.modules.utils.testing.ExtendedMockitoRule; -import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayManagerService.DeviceStateListener; @@ -218,6 +218,9 @@ public class DisplayManagerServiceTest { @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + @Rule(order = 2) + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + private Context mContext; private Resources mResources; @@ -354,6 +357,7 @@ public class DisplayManagerServiceTest { @Mock SensorManager mSensorManager; @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; @Mock PackageManagerInternal mMockPackageManagerInternal; + @Mock DisplayManagerInternal mMockDisplayManagerInternal; @Mock DisplayAdapter mMockDisplayAdapter; @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @@ -371,20 +375,20 @@ public class DisplayManagerServiceTest { when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false); mSetFlagsRule.disableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR); - LocalServices.removeServiceForTest(InputManagerInternal.class); - LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal); - LocalServices.removeServiceForTest(WindowManagerInternal.class); - LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal); - LocalServices.removeServiceForTest(LightsManager.class); - LocalServices.addService(LightsManager.class, mMockLightsManager); - LocalServices.removeServiceForTest(SensorManagerInternal.class); - LocalServices.addService(SensorManagerInternal.class, mMockSensorManagerInternal); - LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); - LocalServices.addService( + mLocalServiceKeeperRule.overrideLocalService( + InputManagerInternal.class, mMockInputManagerInternal); + mLocalServiceKeeperRule.overrideLocalService( + WindowManagerInternal.class, mMockWindowManagerInternal); + mLocalServiceKeeperRule.overrideLocalService( + LightsManager.class, mMockLightsManager); + mLocalServiceKeeperRule.overrideLocalService( + SensorManagerInternal.class, mMockSensorManagerInternal); + mLocalServiceKeeperRule.overrideLocalService( VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); - // TODO: b/287945043 + mLocalServiceKeeperRule.overrideLocalService( + PackageManagerInternal.class, mMockPackageManagerInternal); + mLocalServiceKeeperRule.overrideLocalService( + DisplayManagerInternal.class, mMockDisplayManagerInternal); Display display = mock(Display.class); when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments()); when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class)); @@ -2629,11 +2633,19 @@ public class DisplayManagerServiceTest { // Create default display device createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL); callback.waitForExpectedEvent(); + + callback.expectsEvent(EVENT_DISPLAY_ADDED); FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL); + callback.waitForExpectedEvent(); + + callback.expectsEvent(EVENT_DISPLAY_REMOVED); + displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + callback.waitForExpectedEvent(); + + callback.expectsEvent(EVENT_DISPLAY_ADDED); LogicalDisplay display = logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true); - callback.expectsEvent(EVENT_DISPLAY_ADDED); logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true); logicalDisplayMapper.updateLogicalDisplays(); callback.waitForExpectedEvent(); @@ -2656,6 +2668,7 @@ public class DisplayManagerServiceTest { LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper(); FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(); bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS); + displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); callback.expectsEvent(EVENT_DISPLAY_ADDED); // Create default display device createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL); @@ -2669,7 +2682,6 @@ public class DisplayManagerServiceTest { logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true); logicalDisplayMapper.updateLogicalDisplays(); callback.waitForExpectedEvent(); - callback.clear(); assertThrows(SecurityException.class, () -> bs.disableConnectedDisplay(displayId)); } @@ -2840,6 +2852,7 @@ public class DisplayManagerServiceTest { float brightness1 = 0.3f; float brightness2 = 0.45f; + waitForIdleHandler(mPowerHandler); int userId1 = 123; int userId2 = 456; @@ -2849,8 +2862,8 @@ public class DisplayManagerServiceTest { userInfo2.id = userId2; when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345); when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678); - final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1); - final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2); + final SystemService.TargetUser user1 = new SystemService.TargetUser(userInfo1); + final SystemService.TargetUser user2 = new SystemService.TargetUser(userInfo2); // The same brightness will be restored for a user only if auto-brightness is off, // otherwise the current lux will be used to determine the brightness. @@ -2858,20 +2871,20 @@ public class DisplayManagerServiceTest { Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); - displayManager.onUserSwitching(to, from); + displayManager.onUserSwitching(/* from= */ user2, /* to= */ user1); waitForIdleHandler(mPowerHandler); displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness1); - displayManager.onUserSwitching(from, to); + displayManager.onUserSwitching(/* from= */ user1, /* to= */ user2); waitForIdleHandler(mPowerHandler); displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness2); - displayManager.onUserSwitching(to, from); + displayManager.onUserSwitching(/* from= */ user2, /* to= */ user1); waitForIdleHandler(mPowerHandler); assertEquals(brightness1, displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), FLOAT_TOLERANCE); - displayManager.onUserSwitching(from, to); + displayManager.onUserSwitching(/* from= */ user1, /* to= */ user2); waitForIdleHandler(mPowerHandler); assertEquals(brightness2, displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), @@ -3005,6 +3018,7 @@ public class DisplayManagerServiceTest { new DisplayManagerService(mContext, mBasicInjector); displayManager.systemReady(false /* safeMode */); + displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */); ArgumentMatcher<IntentFilter> matchesFilter = (filter) -> Intent.ACTION_SETTING_RESTORED.equals(filter.getAction(0)); verify(mContext, times(0)).registerReceiver(any(BroadcastReceiver.class), @@ -3370,7 +3384,7 @@ public class DisplayManagerServiceTest { void waitForExpectedEvent(Duration timeout) { try { - assertWithMessage("Event '" + mExpectedEvent + "' is received.") + assertWithMessage("Expected '" + mExpectedEvent + "'") .that(mLatch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)).isTrue(); } catch (InterruptedException ex) { throw new AssertionError("Waiting for expected event interrupted", ex); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt index b182ccef091e..0cf0850f0882 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt @@ -19,6 +19,7 @@ package com.android.server.display.mode import android.content.Context import android.content.ContextWrapper import android.hardware.display.BrightnessInfo +import android.util.SparseBooleanArray import android.view.Display import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest @@ -62,8 +63,11 @@ class BrightnessObserverTest { whenever(mockFlags.isVsyncLowLightVoteEnabled).thenReturn(testCase.vsyncLowLightVoteEnabled) val displayModeDirector = DisplayModeDirector( spyContext, testHandler, mockInjector, mockFlags) + val vrrByDisplay = SparseBooleanArray() + vrrByDisplay.put(Display.DEFAULT_DISPLAY, testCase.vrrSupported) + displayModeDirector.injectVrrByDisplay(vrrByDisplay) val brightnessObserver = displayModeDirector.BrightnessObserver( - spyContext, testHandler, mockInjector, testCase.vrrSupported, mockFlags) + spyContext, testHandler, mockInjector, mockFlags) brightnessObserver.onRefreshRateSettingChangedLocked(0.0f, 120.0f) brightnessObserver.updateBlockingZoneThresholds(mockDeviceConfig, false) diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index fbc38a24b8a5..0efd04657033 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -338,8 +338,6 @@ public class DisplayModeDirectorTest { .thenReturn(false); when(resources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled)) .thenReturn(false); - when(resources.getBoolean(R.bool.config_supportsDvrr)) - .thenReturn(false); when(resources.getInteger(R.integer.config_displayWhiteBalanceBrightnessFilterHorizon)) .thenReturn(10000); when(resources.getInteger(R.integer.config_defaultPeakRefreshRate)) @@ -393,41 +391,87 @@ public class DisplayModeDirectorTest { private DisplayModeDirector createDirectorFromRefreshRateArray( float[] refreshRates, int baseModeId, float defaultRefreshRate) { - Display.Mode[] modes = new Display.Mode[refreshRates.length]; - Display.Mode defaultMode = null; - for (int i = 0; i < refreshRates.length; i++) { - modes[i] = new Display.Mode( - /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]); - if (refreshRates[i] == defaultRefreshRate) { - defaultMode = modes[i]; - } - } + return createDirectorFromRefreshRateArray(refreshRates, baseModeId, defaultRefreshRate, + new int[]{DISPLAY_ID}); + } + + private DisplayModeDirector createDirectorFromRefreshRateArray( + float[] refreshRates, int baseModeId, float defaultRefreshRate, int[] displayIds) { + Display.Mode[] modes = createDisplayModes(refreshRates, baseModeId); + Display.Mode defaultMode = getDefaultMode(modes, defaultRefreshRate); + assertThat(defaultMode).isNotNull(); - return createDirectorFromModeArray(modes, defaultMode); + return createDirectorFromModeArray(modes, defaultMode, displayIds); } private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes, Display.Mode defaultMode) { + return createDirectorFromModeArray(modes, defaultMode, new int[]{DISPLAY_ID}); + } + + private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes, + Display.Mode defaultMode, int[] displayIds) { DisplayModeDirector director = new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags); director.setLoggingEnabled(true); + setupModesForDisplays(director, displayIds , modes, defaultMode); + return director; + } + + private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) { + return createDirectorFromRefreshRateArray( + createRefreshRateRanges(minFps, maxFps), + /*baseModeId=*/minFps, + /*defaultRefreshRate=*/minFps, + new int[]{DISPLAY_ID}); + } + + private DisplayModeDirector createDirectorFromFpsRange( + int minFps, int maxFps, int[] displayIds) { + return createDirectorFromRefreshRateArray( + createRefreshRateRanges(minFps, maxFps), + /*baseModeId=*/minFps, + /*defaultRefreshRate=*/minFps, + displayIds); + } + + private void setupModesForDisplays(DisplayModeDirector director, int[] displayIds, + Display.Mode[] modes, Display.Mode defaultMode) { SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>(); - supportedModesByDisplay.put(DISPLAY_ID, modes); - director.injectSupportedModesByDisplay(supportedModesByDisplay); SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>(); - defaultModesByDisplay.put(DISPLAY_ID, defaultMode); + for (int displayId: displayIds) { + supportedModesByDisplay.put(displayId, modes); + defaultModesByDisplay.put(displayId, defaultMode); + } + director.injectSupportedModesByDisplay(supportedModesByDisplay); director.injectDefaultModeByDisplay(defaultModesByDisplay); - return director; } - private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) { + private Display.Mode[] createDisplayModes(float[] refreshRates, int baseModeId) { + Display.Mode[] modes = new Display.Mode[refreshRates.length]; + for (int i = 0; i < refreshRates.length; i++) { + modes[i] = new Display.Mode( + /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]); + } + return modes; + } + + private Display.Mode getDefaultMode(Display.Mode[] modes, float defaultRefreshRate) { + for (Display.Mode mode : modes) { + if (mode.getRefreshRate() == defaultRefreshRate) { + return mode; + } + } + return null; + } + + private float[] createRefreshRateRanges(int minFps, int maxFps) { int numRefreshRates = maxFps - minFps + 1; float[] refreshRates = new float[numRefreshRates]; for (int i = 0; i < numRefreshRates; i++) { refreshRates[i] = minFps + i; } - return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps, - /*defaultRefreshRate=*/minFps); + return refreshRates; } @Test @@ -1893,6 +1937,7 @@ public class DisplayModeDirectorTest { mInjector.mDisplayInfo.displayId = DISPLAY_ID_2; DisplayModeDirector director = createDirectorFromModeArray(TEST_MODES, DEFAULT_MODE_60); + director.start(createMockSensorManager()); SparseArray<Vote> votes = new SparseArray<>(); votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 50f)); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index 92016dfc631b..d0dd9218eb17 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -39,6 +39,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.res.Resources; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfigInterface; @@ -426,8 +427,12 @@ public class DisplayObserverTest { return true; }).when(mInjector).getDisplayInfo(eq(EXTERNAL_DISPLAY), /*displayInfo=*/ any()); - doAnswer(c -> mock(SensorManagerInternal.class)).when(mInjector).getSensorManagerInternal(); + doAnswer(c -> mock(SensorManagerInternal.class)) + .when(mInjector).getSensorManagerInternal(); doAnswer(c -> mock(DeviceConfigInterface.class)).when(mInjector).getDeviceConfig(); + doAnswer(c -> mock(DisplayManagerInternal.class)) + .when(mInjector).getDisplayManagerInternal(); + mDefaultDisplay = mock(Display.class); when(mDefaultDisplay.getDisplayId()).thenReturn(DEFAULT_DISPLAY); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt index 230317ba738b..196a20231fbb 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt @@ -19,6 +19,8 @@ package com.android.server.display.mode import android.content.Context import android.content.ContextWrapper import android.provider.Settings +import android.util.SparseBooleanArray +import android.view.Display import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest import com.android.internal.util.test.FakeSettingsProvider @@ -39,7 +41,6 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(TestParameterInjector::class) class SettingsObserverTest { - @get:Rule val mockitoRule = MockitoJUnit.rule() @@ -68,8 +69,11 @@ class SettingsObserverTest { val displayModeDirector = DisplayModeDirector( spyContext, testHandler, mockInjector, mockFlags) + val vrrByDisplay = SparseBooleanArray() + vrrByDisplay.put(Display.DEFAULT_DISPLAY, testCase.vrrSupported) + displayModeDirector.injectVrrByDisplay(vrrByDisplay) val settingsObserver = displayModeDirector.SettingsObserver( - spyContext, testHandler, testCase.dvrrSupported, mockFlags) + spyContext, testHandler, mockFlags) settingsObserver.onChange( false, Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE), 1) @@ -79,7 +83,7 @@ class SettingsObserverTest { } enum class SettingsObserverTestCase( - val dvrrSupported: Boolean, + val vrrSupported: Boolean, val vsyncLowPowerVoteEnabled: Boolean, val lowPowerModeEnabled: Boolean, internal val expectedVote: Vote? diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java index b203cf640097..c4a042370de5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -38,7 +38,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.Manifest; -import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.app.backup.ISelectBackupTransportCallback; import android.app.job.JobScheduler; @@ -59,10 +58,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import com.android.server.backup.utils.RandomAccessFileUtils; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -74,6 +75,7 @@ import org.mockito.quality.Strictness; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.io.Writer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -85,8 +87,6 @@ public class BackupManagerServiceTest { "class"); private static final int NON_SYSTEM_USER = UserHandle.USER_SYSTEM + 1; - @UserIdInt - private int mUserId; @Mock private UserBackupManagerService mSystemUserBackupManagerService; @Mock @@ -94,8 +94,6 @@ public class BackupManagerServiceTest { @Mock private Context mContextMock; @Mock - private PrintWriter mPrintWriterMock; - @Mock private UserManager mUserManagerMock; @Mock private UserInfo mUserInfoMock; @@ -104,6 +102,7 @@ public class BackupManagerServiceTest { private BackupManagerServiceTestable mService; private BackupManagerService.Lifecycle mServiceLifecycle; + private FakePrintWriter mFakePrintWriter; private static File sTestDir; private MockitoSession mSession; @@ -114,6 +113,7 @@ public class BackupManagerServiceTest { this) .strictness(Strictness.LENIENT) .spyStatic(UserBackupManagerService.class) + .spyStatic(DumpUtils.class) .startMocking(); doReturn(mSystemUserBackupManagerService).when( () -> UserBackupManagerService.createAndInitializeService( @@ -122,8 +122,7 @@ public class BackupManagerServiceTest { () -> UserBackupManagerService.createAndInitializeService(eq(NON_SYSTEM_USER), any(), any(), any())); - mUserId = UserHandle.USER_SYSTEM; - + when(mNonSystemUserBackupManagerService.getUserId()).thenReturn(NON_SYSTEM_USER); when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock); when(mUserManagerMock.getUserInfo(NON_SYSTEM_USER)).thenReturn(mUserInfoMock); // Null main user means there is no main user on the device. @@ -139,6 +138,8 @@ public class BackupManagerServiceTest { when(mContextMock.getSystemService(Context.JOB_SCHEDULER_SERVICE)) .thenReturn(mock(JobScheduler.class)); + + mFakePrintWriter = new FakePrintWriter(); } @After @@ -552,7 +553,8 @@ public class BackupManagerServiceTest { } }; - mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener); + mService.selectBackupTransportAsyncForUser( + UserHandle.USER_SYSTEM, TRANSPORT_COMPONENT_NAME, listener); assertEquals(BackupManager.ERROR_BACKUP_NOT_ALLOWED, (int) future.get(5, TimeUnit.SECONDS)); } @@ -560,9 +562,10 @@ public class BackupManagerServiceTest { @Test public void selectBackupTransportAsyncForUser_beforeUserUnlockedWithNullListener_doesNotThrow() throws Exception { - createBackupManagerServiceAndUnlockSystemUser(); + mService = new BackupManagerServiceTestable(mContextMock); - mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, null); + mService.selectBackupTransportAsyncForUser( + UserHandle.USER_SYSTEM, TRANSPORT_COMPONENT_NAME, null); // No crash. } @@ -570,13 +573,11 @@ public class BackupManagerServiceTest { @Test public void selectBackupTransportAsyncForUser_beforeUserUnlockedListenerThrowing_doesNotThrow() throws Exception { - createBackupManagerServiceAndUnlockSystemUser(); - + mService = new BackupManagerServiceTestable(mContextMock); ISelectBackupTransportCallback.Stub listener = new ISelectBackupTransportCallback.Stub() { @Override - public void onSuccess(String transportName) { - } + public void onSuccess(String transportName) {} @Override public void onFailure(int reason) throws RemoteException { @@ -584,55 +585,91 @@ public class BackupManagerServiceTest { } }; - mService.selectBackupTransportAsyncForUser(mUserId, TRANSPORT_COMPONENT_NAME, listener); + mService.selectBackupTransportAsyncForUser( + UserHandle.USER_SYSTEM, TRANSPORT_COMPONENT_NAME, listener); // No crash. } @Test - public void dump_callerDoesNotHaveDumpPermission_ignored() { + public void dump_callerDoesNotHaveDumpOrUsageStatsPermission_ignored() { + mockDumpPermissionsGranted(false); createBackupManagerServiceAndUnlockSystemUser(); - when(mContextMock.checkCallingOrSelfPermission( - Manifest.permission.DUMP)).thenReturn( - PackageManager.PERMISSION_DENIED); + when(mContextMock.checkCallingOrSelfPermission(Manifest.permission.DUMP)) + .thenReturn(PackageManager.PERMISSION_DENIED); - mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]); + mService.dump(mFileDescriptorStub, mFakePrintWriter, new String[0]); verify(mSystemUserBackupManagerService, never()).dump(any(), any(), any()); verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any()); } @Test - public void dump_callerDoesNotHavePackageUsageStatsPermission_ignored() { + public void dump_forOneUser_callerDoesNotHaveInteractAcrossUsersFullPermission_ignored() { + mockDumpPermissionsGranted(true); createBackupManagerServiceAndUnlockSystemUser(); - when(mContextMock.checkCallingOrSelfPermission( - Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn( - PackageManager.PERMISSION_DENIED); + mService.setBackupServiceActive(NON_SYSTEM_USER, true); + simulateUserUnlocked(NON_SYSTEM_USER); + doThrow(new SecurityException()) + .when(mContextMock) + .enforceCallingOrSelfPermission( + eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString()); - mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]); + String[] args = new String[] {"--user", Integer.toString(NON_SYSTEM_USER)}; + Assert.assertThrows( + SecurityException.class, + () -> mService.dump(mFileDescriptorStub, mFakePrintWriter, args)); + verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any()); + } + + @Test + public void dump_forOneUser_callerHasInteractAcrossUsersFullPermission_dumpsSpecifiedUser() { + mockDumpPermissionsGranted(true); + createBackupManagerServiceAndUnlockSystemUser(); + mService.setBackupServiceActive(NON_SYSTEM_USER, true); + simulateUserUnlocked(NON_SYSTEM_USER); + + String[] args = new String[] {"--user", Integer.toString(UserHandle.USER_SYSTEM)}; + mService.dump(mFileDescriptorStub, mFakePrintWriter, args); + + verify(mSystemUserBackupManagerService).dump(any(), any(), any()); + } + + @Test + public void dump_users_callerHasInteractAcrossUsersFullPermission_dumpsUsers() { + mockDumpPermissionsGranted(true); + createBackupManagerServiceAndUnlockSystemUser(); + mService.setBackupServiceActive(NON_SYSTEM_USER, true); + simulateUserUnlocked(NON_SYSTEM_USER); + + String[] args = new String[] {"users"}; + mService.dump(mFileDescriptorStub, mFakePrintWriter, args); + + // Check that dump() invocations are not called on user's Backup service, + // as 'dumpsys backup users' only list users for whom Backup service is running. verify(mSystemUserBackupManagerService, never()).dump(any(), any(), any()); verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any()); + assertThat(mFakePrintWriter.mPrintedSoFar) + .isEqualTo("Backup Manager is running for users: 0 1"); } - /** - * Test that {@link BackupManagerService#dump(FileDescriptor, PrintWriter, String[])} dumps - * system user information before non-system user information. - */ @Test - public void testDump_systemUserFirst() { + public void dump_allUsers_dumpsSystemUserFirst() { + mockDumpPermissionsGranted(true); createBackupManagerServiceAndUnlockSystemUser(); mService.setBackupServiceActive(NON_SYSTEM_USER, true); simulateUserUnlocked(NON_SYSTEM_USER); + String[] args = new String[0]; - mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args); + mService.dump(mFileDescriptorStub, mFakePrintWriter, args); InOrder inOrder = inOrder(mSystemUserBackupManagerService, mNonSystemUserBackupManagerService); inOrder.verify(mSystemUserBackupManagerService) - .dump(mFileDescriptorStub, mPrintWriterMock, args); + .dump(mFileDescriptorStub, mFakePrintWriter, args); inOrder.verify(mNonSystemUserBackupManagerService) - .dump(mFileDescriptorStub, mPrintWriterMock, args); + .dump(mFileDescriptorStub, mFakePrintWriter, args); inOrder.verifyNoMoreInteractions(); } @@ -753,6 +790,11 @@ public class BackupManagerServiceTest { return new File(sTestDir, "rememberActivated-" + userId); } + private static void mockDumpPermissionsGranted(boolean granted) { + doReturn(granted) + .when(() -> DumpUtils.checkDumpAndUsageStatsPermission(any(), any(), any())); + } + private static class BackupManagerServiceTestable extends BackupManagerService { static boolean sBackupDisabled = false; static int sCallingUserId = -1; @@ -803,4 +845,17 @@ public class BackupManagerServiceTest { runnable.run(); } } + + private static class FakePrintWriter extends PrintWriter { + String mPrintedSoFar = ""; + + FakePrintWriter() { + super(Writer.nullWriter()); + } + + @Override + public void print(String s) { + mPrintedSoFar = mPrintedSoFar + s; + } + } } 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 671472d619d7..6df4907af93c 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 @@ -1978,7 +1978,7 @@ public class QuotaControllerTest { } @Test - public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() { + public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() { setDischarging(); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; @@ -2021,7 +2021,7 @@ public class QuotaControllerTest { } @Test - public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() { + public void testIsWithinQuotaLocked_OverDuration_OverJobCount() { setDischarging(); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; @@ -2167,73 +2167,6 @@ public class QuotaControllerTest { } @Test - public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() { - setDischarging(); - - JobStatus jobRunning = createJobStatus( - "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1); - JobStatus jobPending = createJobStatus( - "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2); - setStandbyBucket(WORKING_INDEX, jobRunning, jobPending); - - setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10); - - long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false); - - final ExecutionStats stats; - synchronized (mQuotaController.mLock) { - stats = mQuotaController.getExecutionStatsLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(10, stats.jobCountLimit); - assertEquals(9, stats.bgJobCountInWindow); - } - - when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true); - when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false); - - InOrder inOrder = inOrder(mJobSchedulerService); - trackJobs(jobRunning, jobPending); - // UID in the background. - setProcessState(ActivityManager.PROCESS_STATE_SERVICE); - // Start the job. - synchronized (mQuotaController.mLock) { - mQuotaController.prepareForExecutionLocked(jobRunning); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - // Wait for some extra time to allow for job processing. - ArraySet<JobStatus> expected = new ArraySet<>(); - expected.add(jobPending); - inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) - .onControllerStateChanged(eq(expected)); - - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning)); - assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); - assertTrue(jobRunning.isReady()); - assertFalse(mQuotaController.isWithinQuotaLocked(jobPending)); - assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); - assertFalse(jobPending.isReady()); - assertEquals(10, stats.bgJobCountInWindow); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobRunning, null); - } - - synchronized (mQuotaController.mLock) { - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(10, stats.bgJobCountInWindow); - } - } - - @Test public void testIsWithinQuotaLocked_TimingSession() { setDischarging(); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -4718,7 +4651,7 @@ public class QuotaControllerTest { // Handler is told to check when the quota will be consumed, not when the initial // remaining time is over. verify(handler, atLeast(1)).sendMessageDelayed( - argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA), + argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA), eq(10 * SECOND_IN_MILLIS)); verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); @@ -6685,7 +6618,7 @@ public class QuotaControllerTest { // Handler is told to check when the quota will be consumed, not when the initial // remaining time is over. verify(handler, atLeast(1)).sendMessageDelayed( - argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA), + argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA), eq(10 * SECOND_IN_MILLIS)); verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 759a974bd41f..79f1574105ba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -34,6 +34,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -58,6 +59,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.util.Log; @@ -139,7 +141,8 @@ public final class UserManagerServiceTest { .mockStatic(Settings.Secure.class) .build(); - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( + SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); private final Object mPackagesLock = new Object(); private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation() @@ -584,10 +587,12 @@ public final class UserManagerServiceTest { public void testAutoLockPrivateProfile() { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + int mainUser = mUms.getMainUserId(); + assumeTrue(mUms.canAddPrivateProfile(mainUser)); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, - USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null); Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync( eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(), any()); @@ -604,10 +609,12 @@ public final class UserManagerServiceTest { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + int mainUser = mUms.getMainUserId(); + assumeTrue(mUms.canAddPrivateProfile(mainUser)); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, - USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null); mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK); Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync( eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(), @@ -625,6 +632,7 @@ public final class UserManagerServiceTest { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + assumeTrue(mUms.canAddPrivateProfile(0)); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, @@ -644,10 +652,12 @@ public final class UserManagerServiceTest { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + int mainUser = mUms.getMainUserId(); + assumeTrue(mUms.canAddPrivateProfile(mainUser)); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, - USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null); mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true); @@ -664,13 +674,15 @@ public final class UserManagerServiceTest { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + int mainUser = mUms.getMainUserId(); + assumeTrue(mUms.canAddPrivateProfile(mainUser)); UserManagerService mSpiedUms = spy(mUms); mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY); when(mPowerManager.isInteractive()).thenReturn(false); UserInfo privateProfileUser = mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, - USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null); Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace( eq(privateProfileUser.getUserHandle().getIdentifier()), any(), anyLong()); @@ -702,8 +714,10 @@ public final class UserManagerServiceTest { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + int mainUser = mUms.getMainUserId(); + assumeTrue(mUms.canAddPrivateProfile(mainUser)); mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, - USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null); // Set the preference to auto lock on device lock mUms.setOrUpdateAutoLockPreferenceForPrivateProfile( @@ -754,6 +768,7 @@ public final class UserManagerServiceTest { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_HIDING_PROFILES); + assumeTrue(mUms.canAddPrivateProfile(0)); UserInfo privateProfileUser = mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", USER_TYPE_PROFILE_PRIVATE, 0, 0, null); @@ -763,23 +778,23 @@ public final class UserManagerServiceTest { } @Test + @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); - mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); UserManagerService mSpiedUms = spy(mUms); + assumeTrue(mUms.isHeadlessSystemUserMode()); int mainUser = mSpiedUms.getMainUserId(); - doReturn(true).when(mSpiedUms).isHeadlessSystemUserMode(); - assertThat(mSpiedUms.canAddPrivateProfile(mainUser)).isTrue(); + // Check whether private space creation is blocked on the device + assumeTrue(mSpiedUms.canAddPrivateProfile(mainUser)); assertThat(mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow( PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)).isNotNull(); } @Test + @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); - mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); + assumeTrue(mUms.canAddMoreUsersOfType(USER_TYPE_FULL_SECONDARY)); UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0); assertThat(mUms.canAddPrivateProfile(user.id)).isFalse(); assertThrows(ServiceSpecificException.class, @@ -788,52 +803,48 @@ public final class UserManagerServiceTest { } @Test + @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); - mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt()); int mainUser = mUms.getMainUserId(); - assertThat(mUms.canAddPrivateProfile(0)).isFalse(); + assertThat(mUms.canAddPrivateProfile(mainUser)).isFalse(); assertThrows(ServiceSpecificException.class, () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)); } @Test + @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); - mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt()); int mainUser = mUms.getMainUserId(); - assertThat(mUms.canAddPrivateProfile(0)).isFalse(); + assertThat(mUms.canAddPrivateProfile(mainUser)).isFalse(); assertThrows(ServiceSpecificException.class, () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)); } @Test + @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); - mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt()); int mainUser = mUms.getMainUserId(); - assertThat(mUms.canAddPrivateProfile(0)).isFalse(); + assertThat(mUms.canAddPrivateProfile(mainUser)).isFalse(); assertThrows(ServiceSpecificException.class, () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)); } @Test + @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() { - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); - mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt()); int mainUser = mUms.getMainUserId(); - assertThat(mUms.canAddPrivateProfile(0)).isFalse(); + assertThat(mUms.canAddPrivateProfile(mainUser)).isFalse(); assertThrows(ServiceSpecificException.class, () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)); diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index 51c9d0ae5e5d..f2b4136c51ed 100644 --- a/services/tests/powerstatstests/Android.bp +++ b/services/tests/powerstatstests/Android.bp @@ -4,58 +4,6 @@ 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/AmbientDisplayPowerCalculatorTest.java", - "src/com/android/server/power/stats/AudioPowerCalculatorTest.java", - "src/com/android/server/power/stats/BatteryChargeCalculatorTest.java", - "src/com/android/server/power/stats/BatteryStatsCounterTest.java", - "src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java", - "src/com/android/server/power/stats/BatteryStatsDualTimerTest.java", - "src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java", - "src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java", - "src/com/android/server/power/stats/BatteryStatsHistoryTest.java", - "src/com/android/server/power/stats/BatteryStatsImplTest.java", - "src/com/android/server/power/stats/BatteryStatsNoteTest.java", - "src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java", - "src/com/android/server/power/stats/BatteryStatsSensorTest.java", - "src/com/android/server/power/stats/BatteryStatsServTest.java", - "src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java", - "src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java", - "src/com/android/server/power/stats/BatteryStatsTimerTest.java", - "src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java", - "src/com/android/server/power/stats/BatteryUsageStatsTest.java", - "src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java", - "src/com/android/server/power/stats/CameraPowerCalculatorTest.java", - "src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java", - "src/com/android/server/power/stats/CpuPowerCalculatorTest.java", - "src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java", - "src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java", - "src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java", - "src/com/android/server/power/stats/GnssPowerCalculatorTest.java", - "src/com/android/server/power/stats/IdlePowerCalculatorTest.java", - "src/com/android/server/power/stats/LongSamplingCounterArrayTest.java", - "src/com/android/server/power/stats/LongSamplingCounterTest.java", - "src/com/android/server/power/stats/MemoryPowerCalculatorTest.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/PowerStatsExporterTest.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", - "src/com/android/server/power/stats/ScreenPowerCalculatorTest.java", - "src/com/android/server/power/stats/SensorPowerCalculatorTest.java", - "src/com/android/server/power/stats/UserPowerCalculatorTest.java", - "src/com/android/server/power/stats/VideoPowerCalculatorTest.java", - "src/com/android/server/power/stats/WakelockPowerCalculatorTest.java", - "src/com/android/server/power/stats/WifiPowerCalculatorTest.java", - ], -} - android_test { name: "PowerStatsTests", @@ -79,7 +27,6 @@ android_test { "servicestests-utils", "platform-test-annotations", "flag-junit", - "ravenwood-junit", ], libs: [ @@ -112,17 +59,20 @@ android_ravenwood_test { name: "PowerStatsTestsRavenwood", static_libs: [ "services.core", - "modules-utils-binary-xml", + "coretests-aidl", + "ravenwood-junit", + "truth", "androidx.annotation_annotation", "androidx.test.rules", - "truth", + "androidx.test.uiautomator_uiautomator", + "modules-utils-binary-xml", + "flag-junit", ], srcs: [ - ":power_stats_ravenwood_tests", - - "src/com/android/server/power/stats/BatteryUsageStatsRule.java", - "src/com/android/server/power/stats/MockBatteryStatsImpl.java", - "src/com/android/server/power/stats/MockClock.java", + "src/com/android/server/power/stats/*.java", + ], + java_resources: [ + "res/xml/power_profile*.xml", ], auto_gen_config: true, } diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING index 6d3db1cb6c23..fb243616292d 100644 --- a/services/tests/powerstatstests/TEST_MAPPING +++ b/services/tests/powerstatstests/TEST_MAPPING @@ -12,7 +12,11 @@ "ravenwood-presubmit": [ { "name": "PowerStatsTestsRavenwood", - "host": true + "host": true, + "options": [ + {"include-filter": "com.android.server.power.stats"}, + {"exclude-annotation": "android.platform.test.annotations.DisabledOnRavenwood"} + ] } ], "postsubmit": [ diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java index ca7de7c3f325..9975190424b5 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.os.BatteryConsumer; import android.os.PersistableBundle; +import android.util.SparseArray; import android.util.Xml; import androidx.test.filters.SmallTest; @@ -43,6 +44,9 @@ public class AggregatedPowerStatsTest { private static final int TEST_POWER_COMPONENT = 1077; private static final int APP_1 = 27; private static final int APP_2 = 42; + private static final int COMPONENT_STATE_0 = 0; + private static final int COMPONENT_STATE_1 = 1; + private static final int COMPONENT_STATE_2 = 2; private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; private PowerStats.Descriptor mPowerComponentDescriptor; @@ -59,8 +63,10 @@ public class AggregatedPowerStatsTest { AggregatedPowerStatsConfig.STATE_SCREEN, AggregatedPowerStatsConfig.STATE_PROCESS_STATE); - mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3, - PersistableBundle.forPair("speed", "fast")); + SparseArray<String> stateLabels = new SparseArray<>(); + stateLabels.put(COMPONENT_STATE_1, "one"); + mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, + stateLabels, 1, 3, PersistableBundle.forPair("speed", "fast")); } @Test @@ -107,6 +113,9 @@ public class AggregatedPowerStatsTest { ps.stats[0] = 100; ps.stats[1] = 987; + ps.stateStats.put(COMPONENT_STATE_0, new long[]{1111}); + ps.stateStats.put(COMPONENT_STATE_1, new long[]{5000}); + ps.uidStats.put(APP_1, new long[]{389, 0, 739}); ps.uidStats.put(APP_2, new long[]{278, 314, 628}); @@ -120,11 +129,14 @@ public class AggregatedPowerStatsTest { ps.stats[0] = 444; ps.stats[1] = 0; + ps.stateStats.clear(); + ps.stateStats.put(COMPONENT_STATE_1, new long[]{1000}); + ps.stateStats.put(COMPONENT_STATE_2, new long[]{9000}); + ps.uidStats.put(APP_1, new long[]{0, 0, 400}); ps.uidStats.put(APP_2, new long[]{100, 200, 300}); stats.addPowerStats(ps, 5000); - return stats; } @@ -147,6 +159,31 @@ public class AggregatedPowerStatsTest { AggregatedPowerStatsConfig.SCREEN_STATE_OTHER)) .isEqualTo(new long[]{222, 0}); + assertThat(getStateStats(stats, COMPONENT_STATE_0, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON)) + .isEqualTo(new long[]{1111}); + + assertThat(getStateStats(stats, COMPONENT_STATE_1, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON)) + .isEqualTo(new long[]{5500}); + + assertThat(getStateStats(stats, COMPONENT_STATE_1, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER)) + .isEqualTo(new long[]{500}); + + assertThat(getStateStats(stats, COMPONENT_STATE_2, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_ON)) + .isEqualTo(new long[]{4500}); + + assertThat(getStateStats(stats, COMPONENT_STATE_2, + AggregatedPowerStatsConfig.POWER_STATE_BATTERY, + AggregatedPowerStatsConfig.SCREEN_STATE_OTHER)) + .isEqualTo(new long[]{4500}); + assertThat(getUidDeviceStats(stats, APP_1, AggregatedPowerStatsConfig.POWER_STATE_BATTERY, @@ -191,14 +228,26 @@ public class AggregatedPowerStatsTest { } private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) { - long[] out = new long[states.length]; - stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states); + PowerComponentAggregatedPowerStats powerComponentStats = + stats.getPowerComponentStats(TEST_POWER_COMPONENT); + long[] out = new long[powerComponentStats.getPowerStatsDescriptor().statsArrayLength]; + powerComponentStats.getDeviceStats(out, states); + return out; + } + + private static long[] getStateStats(AggregatedPowerStats stats, int key, int... states) { + PowerComponentAggregatedPowerStats powerComponentStats = + stats.getPowerComponentStats(TEST_POWER_COMPONENT); + long[] out = new long[powerComponentStats.getPowerStatsDescriptor().stateStatsArrayLength]; + powerComponentStats.getStateStats(out, key, states); return out; } private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) { - long[] out = new long[states.length]; - stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states); + PowerComponentAggregatedPowerStats powerComponentStats = + stats.getPowerComponentStats(TEST_POWER_COMPONENT); + long[] out = new long[powerComponentStats.getPowerStatsDescriptor().uidStatsArrayLength]; + powerComponentStats.getUidStats(out, uid, states); return out; } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java index 3ab1c2eab6ca..9b45ca79fdab 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java @@ -16,9 +16,11 @@ package com.android.server.power.stats; - import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + +import android.content.Context; import android.os.BatteryManager; import android.os.BatteryUsageStats; import android.platform.test.ravenwood.RavenwoodRule; @@ -28,6 +30,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.PowerProfile; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,6 +49,11 @@ public class BatteryChargeCalculatorTest { public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0); + @Before + public void setup() { + mStatsRule.getBatteryStats().onSystemReady(mock(Context.class)); + } + @Test public void testDischargeTotals() { // Nominal battery capacity should be ignored diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index 997b7712a9dd..0a9c8c00444a 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java @@ -36,6 +36,7 @@ import android.hardware.power.stats.EnergyConsumerType; import android.hardware.power.stats.EnergyMeasurement; import android.hardware.power.stats.PowerEntity; import android.hardware.power.stats.StateResidencyResult; +import android.platform.test.ravenwood.RavenwoodRule; import android.power.PowerStatsInternal; import android.util.IntArray; import android.util.SparseArray; @@ -47,6 +48,7 @@ import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.util.Arrays; @@ -59,7 +61,10 @@ import java.util.concurrent.CompletableFuture; * atest FrameworksServicesTests:BatteryExternalStatsWorkerTest */ @SuppressWarnings("GuardedBy") +@android.platform.test.annotations.DisabledOnRavenwood public class BatteryExternalStatsWorkerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private BatteryExternalStatsWorker mBatteryExternalStatsWorker; private TestBatteryStatsImpl mBatteryStatsImpl; private TestPowerStatsInternal mPowerStatsInternal; @@ -215,7 +220,8 @@ public class BatteryExternalStatsWorkerTest { public class TestBatteryStatsImpl extends BatteryStatsImpl { public TestBatteryStatsImpl(Context context) { - super(Clock.SYSTEM_CLOCK, null, null, null, null, null, null); + super(new BatteryStatsConfig.Builder().build(), Clock.SYSTEM_CLOCK, null, null, null, + null, null, null); mPowerProfile = new PowerProfile(context, true /* forTest */); SparseArray<int[]> cpusByPolicy = new SparseArray<>(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java index 4d3fcb611f24..ad05b5124955 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java @@ -18,25 +18,37 @@ package com.android.server.power.stats; import static android.os.BatteryStats.STATS_SINCE_CHARGED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import android.app.ActivityManager; import android.os.BatteryStats; import android.os.WorkSource; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArrayMap; import android.view.Display; import androidx.test.filters.SmallTest; -import junit.framework.TestCase; +import org.junit.Rule; +import org.junit.Test; /** * Test BatteryStatsImpl onBatteryBackgroundTimeBase TimeBase. */ -public class BatteryStatsBackgroundStatsTest extends TestCase { +public class BatteryStatsBackgroundStatsTest { + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); private static final int UID = 10500; /** Test that BatteryStatsImpl.Uid.mOnBatteryBackgroundTimeBase works correctly. */ @SmallTest + @Test public void testBgTimeBase() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); @@ -105,6 +117,7 @@ public class BatteryStatsBackgroundStatsTest extends TestCase { /** Test that BatteryStatsImpl.Uid.mOnBatteryScreenOffBackgroundTimeBase works correctly. */ @SmallTest + @Test public void testScreenOffBgTimeBase() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); @@ -153,6 +166,7 @@ public class BatteryStatsBackgroundStatsTest extends TestCase { } @SmallTest + @Test public void testWifiScan() throws Exception { final MockClock clocks = new MockClock(); MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); @@ -195,11 +209,13 @@ public class BatteryStatsBackgroundStatsTest extends TestCase { } @SmallTest + @Test public void testAppBluetoothScan() throws Exception { doTestAppBluetoothScanInternal(new WorkSource(UID)); } @SmallTest + @Test public void testAppBluetoothScan_workChain() throws Exception { WorkSource ws = new WorkSource(); ws.createWorkChain().addNode(UID, "foo"); @@ -275,6 +291,7 @@ public class BatteryStatsBackgroundStatsTest extends TestCase { } @SmallTest + @Test public void testJob() throws Exception { final MockClock clocks = new MockClock(); MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); @@ -336,6 +353,7 @@ public class BatteryStatsBackgroundStatsTest extends TestCase { } @SmallTest + @Test public void testSyncs() throws Exception { final MockClock clocks = new MockClock(); MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java index 3f101a96d36c..4dfc3fcec916 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java @@ -16,20 +16,20 @@ package com.android.server.power.stats; +import static org.junit.Assert.assertEquals; + import android.os.Binder; import android.os.Process; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArraySet; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderTransactionNameResolver; -import junit.framework.TestCase; - +import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Collection; @@ -37,9 +37,14 @@ import java.util.Collection; /** * Test cases for android.os.BatteryStats, system server Binder call stats. */ -@RunWith(AndroidJUnit4.class) @SmallTest -public class BatteryStatsBinderCallStatsTest extends TestCase { +@android.platform.test.annotations.DisabledOnRavenwood(blockedBy = BinderCallsStats.class) +public class BatteryStatsBinderCallStatsTest { + + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); private static final int TRANSACTION_CODE1 = 100; private static final int TRANSACTION_CODE2 = 101; @@ -89,7 +94,6 @@ public class BatteryStatsBinderCallStatsTest extends TestCase { assertEquals(500, value.recordedCpuTimeMicros); } - @Test public void testProportionalSystemServiceUsage_noStatsForSomeMethods() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java index 6e62147ac6c1..eff1b7b852d9 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java @@ -118,7 +118,8 @@ public class BatteryStatsCpuTimesTest { mClocks = new MockClock(); Handler handler = new Handler(Looper.getMainLooper()); mPowerStatsUidResolver = new PowerStatsUidResolver(); - mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks, null, handler, mPowerStatsUidResolver) + mBatteryStatsImpl = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG, + mClocks, null, handler, mPowerStatsUidResolver) .setTestCpuScalingPolicies() .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader) .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader) diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index c58c92b47dd3..e40a3e314e58 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -403,7 +403,7 @@ public class BatteryStatsHistoryTest { @Test public void recordPowerStats() { - PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, 2, + PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, null, 0, 2, new PersistableBundle()); PowerStats powerStats = new PowerStats(descriptor); powerStats.durationMs = 100; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java index 7ae111711b6b..9a64ce19254b 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java @@ -25,15 +25,21 @@ import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.UidBatteryConsumer; +import android.platform.test.ravenwood.RavenwoodRule; +import org.junit.Rule; import org.junit.Test; /** * Test BatteryStatsManager and CellularBatteryStats to ensure that valid data is being reported * and that invalid data is not reported. */ +@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test") public class BatteryStatsManagerTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Test public void testBatteryUsageStatsDataConsistency() { BatteryStatsManager bsm = getContext().getSystemService(BatteryStatsManager.class); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index 07cefa9ae878..afbe9159b66a 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java @@ -170,8 +170,8 @@ public class BatteryStatsNoteTest { public void testNoteStartWakeLocked_isolatedUid() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, - new Handler(Looper.getMainLooper()), uidResolver); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG, + clocks, null, new Handler(Looper.getMainLooper()), uidResolver); int pid = 10; String name = "name"; @@ -212,8 +212,8 @@ public class BatteryStatsNoteTest { public void testNoteStartWakeLocked_isolatedUidRace() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, - new Handler(Looper.getMainLooper()), uidResolver); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG, + clocks, null, new Handler(Looper.getMainLooper()), uidResolver); int pid = 10; String name = "name"; @@ -256,8 +256,8 @@ public class BatteryStatsNoteTest { public void testNoteLongPartialWakelockStart_isolatedUid() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, - new Handler(Looper.getMainLooper()), uidResolver); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG, + clocks, null, new Handler(Looper.getMainLooper()), uidResolver); bi.setRecordAllHistoryLocked(true); bi.forceRecordAllHistory(); @@ -311,8 +311,8 @@ public class BatteryStatsNoteTest { public void testNoteLongPartialWakelockStart_isolatedUidRace() throws Exception { final MockClock clocks = new MockClock(); // holds realtime and uptime in ms PowerStatsUidResolver uidResolver = new PowerStatsUidResolver(); - MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks, null, - new Handler(Looper.getMainLooper()), uidResolver); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(MockBatteryStatsImpl.DEFAULT_CONFIG, + clocks, null, new Handler(Looper.getMainLooper()), uidResolver); bi.setRecordAllHistoryLocked(true); bi.forceRecordAllHistory(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java index a0fb631812f4..d29bf1abd7a3 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java @@ -18,21 +18,32 @@ package com.android.server.power.stats; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; + import android.content.Context; import android.os.BatteryManager; +import android.platform.test.ravenwood.RavenwoodRule; -import androidx.test.InstrumentationRegistry; 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; +import java.io.IOException; +import java.nio.file.Files; + @SmallTest @RunWith(AndroidJUnit4.class) public class BatteryStatsResetTest { + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + private static final int BATTERY_NOMINAL_VOLTAGE_MV = 3700; private static final int BATTERY_CAPACITY_UAH = 4_000_000; private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100; @@ -79,13 +90,11 @@ public class BatteryStatsResetTest { private long mBatteryChargeTimeToFullSeconds; @Before - public void setUp() { - final Context context = InstrumentationRegistry.getContext(); - + public void setUp() throws IOException { mMockClock = new MockClock(); - mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, context.getFilesDir()); - mBatteryStatsImpl.onSystemReady(); - + mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, + Files.createTempDirectory("BatteryStatsResetTest").toFile()); + mBatteryStatsImpl.onSystemReady(mock(Context.class)); // Set up the battery state. Start off with a fully charged plugged in battery. mBatteryStatus = BatteryManager.BATTERY_STATUS_FULL; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java index c4561b16d73c..3931201aaf03 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java @@ -28,6 +28,7 @@ import android.content.pm.UserInfo; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArraySet; import androidx.test.InstrumentationRegistry; @@ -38,6 +39,7 @@ import androidx.test.uiautomator.UiDevice; 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; @@ -46,8 +48,10 @@ import java.util.concurrent.TimeUnit; @LargeTest @RunWith(AndroidJUnit4.class) -@android.platform.test.annotations.IgnoreUnderRavenwood +@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test") public class BatteryStatsUserLifecycleTests { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private static final long POLL_INTERVAL_MS = 500; private static final long USER_REMOVE_TIMEOUT_MS = 5_000; @@ -65,6 +69,10 @@ public class BatteryStatsUserLifecycleTests { @BeforeClass public static void setUpOnce() { + if (RavenwoodRule.isOnRavenwood()) { + return; + } + assumeTrue(UserManager.getMaxSupportedUsers() > 1); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 296ad0e939de..2d7cb2245c0a 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -24,7 +24,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import android.annotation.XmlRes; +import android.content.Context; +import android.content.res.Resources; import android.net.NetworkStats; import android.os.BatteryConsumer; import android.os.BatteryStats; @@ -35,9 +36,9 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.UidBatteryConsumer; import android.os.UserBatteryConsumer; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.SparseArray; - -import androidx.test.InstrumentationRegistry; +import android.util.Xml; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; @@ -47,6 +48,7 @@ import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; import org.mockito.stubbing.Answer; +import org.xmlpull.v1.XmlPullParser; import java.io.File; import java.io.IOException; @@ -81,6 +83,7 @@ public class BatteryUsageStatsRule implements TestRule { private boolean[] mSupportedStandardBuckets; private String[] mCustomPowerComponentNames; private Throwable mThrowable; + private final BatteryStatsImpl.BatteryStatsConfig.Builder mBatteryStatsConfigBuilder; public BatteryUsageStatsRule() { this(0); @@ -94,6 +97,11 @@ public class BatteryUsageStatsRule implements TestRule { mCpusByPolicy.put(4, new int[]{4, 5, 6, 7}); mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000}); mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000}); + mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder() + .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU, + 10000) + .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, + 10000); } private void initBatteryStats() { @@ -107,7 +115,8 @@ public class BatteryUsageStatsRule implements TestRule { } clearDirectory(); } - mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler); + mBatteryStats = new MockBatteryStatsImpl(mBatteryStatsConfigBuilder.build(), + mMockClock, mHistoryDir, mHandler, new PowerStatsUidResolver()); mBatteryStats.setPowerProfile(mPowerProfile); mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); synchronized (mBatteryStats) { @@ -116,8 +125,6 @@ public class BatteryUsageStatsRule implements TestRule { } mBatteryStats.informThatAllExternalStatsAreFlushed(); - mBatteryStats.onSystemReady(); - if (mDisplayCount != -1) { mBatteryStats.setDisplayCountLocked(mDisplayCount); } @@ -148,11 +155,27 @@ public class BatteryUsageStatsRule implements TestRule { return this; } - public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) { - mPowerProfile.forceInitForTesting(InstrumentationRegistry.getContext(), xmlId); + public BatteryUsageStatsRule setTestPowerProfile(String resourceName) { + mPowerProfile.initForTesting(resolveParser(resourceName)); return this; } + public static XmlPullParser resolveParser(String resourceName) { + if (RavenwoodRule.isOnRavenwood()) { + try { + return Xml.resolvePullParser(BatteryUsageStatsRule.class.getClassLoader() + .getResourceAsStream("res/xml/" + resourceName + ".xml")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + Context context = androidx.test.InstrumentationRegistry.getContext(); + Resources resources = context.getResources(); + int resId = resources.getIdentifier(resourceName, "xml", context.getPackageName()); + return resources.getXml(resId); + } + } + public BatteryUsageStatsRule setCpuScalingPolicy(int policy, int[] relatedCpus, int[] frequencies) { if (mDefaultCpuScalingPolicy) { @@ -265,6 +288,12 @@ public class BatteryUsageStatsRule implements TestRule { return this; } + public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent, + long throttleMs) { + mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(powerComponent, throttleMs); + return this; + } + public BatteryUsageStatsRule startWithScreenOn(boolean screenOn) { mScreenOn = screenOn; return this; @@ -291,23 +320,21 @@ public class BatteryUsageStatsRule implements TestRule { } private void before() { - initBatteryStats(); HandlerThread bgThread = new HandlerThread("bg thread"); bgThread.setUncaughtExceptionHandler((thread, throwable)-> { mThrowable = throwable; }); bgThread.start(); mHandler = new Handler(bgThread.getLooper()); - mBatteryStats.setHandler(mHandler); + + initBatteryStats(); mBatteryStats.setOnBatteryInternal(true); mBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0); mBatteryStats.getOnBatteryScreenOffTimeBase().setRunning(!mScreenOn, 0, 0); } private void after() throws Throwable { - if (mHandler != null) { - waitForBackgroundThread(); - } + waitForBackgroundThread(); } public void waitForBackgroundThread() throws Throwable { @@ -316,11 +343,12 @@ public class BatteryUsageStatsRule implements TestRule { } ConditionVariable done = new ConditionVariable(); - mHandler.post(done::open); - assertThat(done.block(10000)).isTrue(); - - if (mThrowable != null) { - throw mThrowable; + if (mHandler.post(done::open)) { + boolean success = done.block(5000); + if (mThrowable != null) { + throw mThrowable; + } + assertThat(success).isTrue(); } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java index 29e2f5ee163a..e4ab227a4840 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java @@ -46,6 +46,7 @@ import android.os.IBinder; import android.os.PowerManager; import android.os.Process; import android.os.SystemClock; +import android.platform.test.ravenwood.RavenwoodRule; import android.provider.Settings; import android.util.ArrayMap; import android.util.DebugUtils; @@ -74,9 +75,11 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; @LargeTest -@RunWith(AndroidJUnit4.class) -@android.platform.test.annotations.IgnoreUnderRavenwood +@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test") public class BstatsCpuTimesValidationTest { + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private static final String TAG = BstatsCpuTimesValidationTest.class.getSimpleName(); private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp"; @@ -112,10 +115,15 @@ public class BstatsCpuTimesValidationTest { private static boolean sCpuFreqTimesAvailable; private static boolean sPerProcStateTimesAvailable; - @Rule public TestName testName = new TestName(); + @Rule(order = 1) + public TestName testName = new TestName(); @BeforeClass public static void setupOnce() throws Exception { + if (RavenwoodRule.isOnRavenwood()) { + return; + } + sContext = InstrumentationRegistry.getContext(); sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG, @@ -127,6 +135,10 @@ public class BstatsCpuTimesValidationTest { @AfterClass public static void tearDownOnce() throws Exception { + if (RavenwoodRule.isOnRavenwood()) { + return; + } + executeCmd("cmd deviceidle whitelist -" + TEST_PKG); if (sBatteryStatsConstsUpdated) { Settings.Global.putString(sContext.getContentResolver(), diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java index 64d5414bf66c..ad2939284471 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java @@ -23,65 +23,127 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; -import android.content.Context; -import android.hardware.power.stats.EnergyConsumer; -import android.hardware.power.stats.EnergyConsumerResult; import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; -import android.power.PowerStatsInternal; +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 com.android.frameworks.powerstatstests.R; +import com.android.internal.os.Clock; import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerStats; 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 org.xmlpull.v1.XmlPullParserException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; +import java.io.IOException; +import java.util.function.IntSupplier; @RunWith(AndroidJUnit4.class) @SmallTest public class CpuPowerStatsCollectorTest { + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + private static final int ISOLATED_UID = 99123; private static final int UID_1 = 42; private static final int UID_2 = 99; - private Context mContext; private final MockClock mMockClock = new MockClock(); private final HandlerThread mHandlerThread = new HandlerThread("test"); private Handler mHandler; private PowerStats mCollectedStats; - private PowerProfile mPowerProfile; + private PowerProfile mPowerProfile = new PowerProfile(); @Mock private PowerStatsUidResolver mUidResolver; @Mock private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader; @Mock - private PowerStatsInternal mPowerStatsInternal; + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; private CpuScalingPolicies mCpuScalingPolicies; + private class TestInjector implements CpuPowerStatsCollector.Injector { + private final int mDefaultCpuPowerBrackets; + private final int mDefaultCpuPowerBracketsPerEnergyConsumer; + + TestInjector(int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { + mDefaultCpuPowerBrackets = defaultCpuPowerBrackets; + mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer; + } + + @Override + public Handler getHandler() { + return mHandler; + } + + @Override + public Clock getClock() { + return mMockClock; + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mUidResolver; + } + + @Override + public CpuScalingPolicies getCpuScalingPolicies() { + return mCpuScalingPolicies; + } + + @Override + public PowerProfile getPowerProfile() { + return mPowerProfile; + } + + @Override + public CpuPowerStatsCollector.KernelCpuStatsReader getKernelCpuStatsReader() { + return mMockKernelCpuStatsReader; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> 3500; + } + + @Override + public int getDefaultCpuPowerBrackets() { + return mDefaultCpuPowerBrackets; + } + + @Override + public int getDefaultCpuPowerBracketsPerEnergyConsumer() { + return mDefaultCpuPowerBracketsPerEnergyConsumer; + } + }; + @Before - public void setup() { + public void setup() throws XmlPullParserException, IOException { MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getContext(); - mHandlerThread.start(); mHandler = mHandlerThread.getThreadHandler(); - when(mMockKernelCpuStatsReader.nativeIsSupportedFeature()).thenReturn(true); + when(mMockKernelCpuStatsReader.isSupportedFeature()).thenReturn(true); when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> { int uid = invocation.getArgument(0); if (uid == ISOLATED_UID) { @@ -90,12 +152,13 @@ public class CpuPowerStatsCollectorTest { return uid; } }); + when(mConsumedEnergyRetriever.getEnergyConsumerIds(anyInt())).thenReturn(new int[0]); } @Test public void powerBrackets_specifiedInPowerProfile() { - mPowerProfile = new PowerProfile(mContext); - mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets); + mPowerProfile.initForTesting( + BatteryUsageStatsRule.resolveParser("power_profile_test_power_brackets")); mCpuScalingPolicies = new CpuScalingPolicies( new SparseArray<>() {{ put(0, new int[]{0}); @@ -114,8 +177,7 @@ public class CpuPowerStatsCollectorTest { @Test public void powerBrackets_default_noEnergyConsumers() { - mPowerProfile = new PowerProfile(mContext); - mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mPowerProfile.initForTesting(BatteryUsageStatsRule.resolveParser("power_profile_test")); mockCpuScalingPolicies(2); CpuPowerStatsCollector collector = createCollector(3, 0); @@ -134,8 +196,7 @@ public class CpuPowerStatsCollectorTest { @Test public void powerBrackets_moreBracketsThanStates() { - mPowerProfile = new PowerProfile(mContext); - mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mPowerProfile.initForTesting(BatteryUsageStatsRule.resolveParser("power_profile_test")); mockCpuScalingPolicies(2); CpuPowerStatsCollector collector = createCollector(8, 0); @@ -146,8 +207,7 @@ public class CpuPowerStatsCollectorTest { @Test public void powerBrackets_energyConsumers() throws Exception { - mPowerProfile = new PowerProfile(mContext); - mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mPowerProfile.initForTesting(BatteryUsageStatsRule.resolveParser("power_profile_test")); mockCpuScalingPolicies(2); mockEnergyConsumers(); @@ -159,8 +219,7 @@ public class CpuPowerStatsCollectorTest { @Test public void powerStatsDescriptor() throws Exception { - mPowerProfile = new PowerProfile(mContext); - mPowerProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + mPowerProfile.initForTesting(BatteryUsageStatsRule.resolveParser("power_profile_test")); mockCpuScalingPolicies(2); mockEnergyConsumers(); @@ -170,8 +229,8 @@ public class CpuPowerStatsCollectorTest { assertThat(descriptor.name).isEqualTo("cpu"); assertThat(descriptor.statsArrayLength).isEqualTo(13); assertThat(descriptor.uidStatsArrayLength).isEqualTo(5); - CpuPowerStatsCollector.CpuStatsArrayLayout layout = - new CpuPowerStatsCollector.CpuStatsArrayLayout(); + CpuPowerStatsLayout layout = + new CpuPowerStatsLayout(); layout.fromExtras(descriptor.extras); long[] deviceStats = new long[descriptor.statsArrayLength]; @@ -209,8 +268,8 @@ public class CpuPowerStatsCollectorTest { mockEnergyConsumers(); CpuPowerStatsCollector collector = createCollector(8, 0); - CpuPowerStatsCollector.CpuStatsArrayLayout layout = - new CpuPowerStatsCollector.CpuStatsArrayLayout(); + CpuPowerStatsLayout layout = + new CpuPowerStatsLayout(); layout.fromExtras(collector.getPowerStatsDescriptor().extras); mockKernelCpuStats(new long[]{1111, 2222, 3333}, @@ -296,10 +355,9 @@ public class CpuPowerStatsCollectorTest { private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { - CpuPowerStatsCollector collector = new CpuPowerStatsCollector(mCpuScalingPolicies, - mPowerProfile, mHandler, mMockKernelCpuStatsReader, mUidResolver, - () -> mPowerStatsInternal, () -> 3500, 60_000, mMockClock, - defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer); + CpuPowerStatsCollector collector = new CpuPowerStatsCollector( + new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer), + 0); collector.addConsumer(stats -> mCollectedStats = stats); collector.setEnabled(true); return collector; @@ -307,7 +365,7 @@ public class CpuPowerStatsCollectorTest { private void mockKernelCpuStats(long[] deviceStats, SparseArray<long[]> uidToCpuStats, long expectedLastUpdateTimestampMs, long newLastUpdateTimestampMs) { - when(mMockKernelCpuStatsReader.nativeReadCpuStats( + when(mMockKernelCpuStatsReader.readCpuStats( any(CpuPowerStatsCollector.KernelCpuStatsCallback.class), any(int[].class), anyLong(), any(long[].class), any(long[].class))) .thenAnswer(invocation -> { @@ -335,63 +393,18 @@ public class CpuPowerStatsCollectorTest { }); } - @SuppressWarnings("unchecked") - private void mockEnergyConsumers() throws Exception { - when(mPowerStatsInternal.getEnergyConsumerInfo()) - .thenReturn(new EnergyConsumer[]{ - new EnergyConsumer() {{ - id = 1; - type = EnergyConsumerType.CPU_CLUSTER; - ordinal = 0; - name = "CPU0"; - }}, - new EnergyConsumer() {{ - id = 2; - type = EnergyConsumerType.CPU_CLUSTER; - ordinal = 1; - name = "CPU4"; - }}, - new EnergyConsumer() {{ - id = 3; - type = EnergyConsumerType.BLUETOOTH; - name = "BT"; - }}, - }); - - CompletableFuture<EnergyConsumerResult[]> future1 = mock(CompletableFuture.class); - when(future1.get(anyLong(), any(TimeUnit.class))) - .thenReturn(new EnergyConsumerResult[]{ - new EnergyConsumerResult() {{ - id = 1; - energyUWs = 1000; - }}, - new EnergyConsumerResult() {{ - id = 2; - energyUWs = 2000; - }} - }); - - CompletableFuture<EnergyConsumerResult[]> future2 = mock(CompletableFuture.class); - when(future2.get(anyLong(), any(TimeUnit.class))) - .thenReturn(new EnergyConsumerResult[]{ - new EnergyConsumerResult() {{ - id = 1; - energyUWs = 1500; - }}, - new EnergyConsumerResult() {{ - id = 2; - energyUWs = 2700; - }} - }); - - when(mPowerStatsInternal.getEnergyConsumedAsync(eq(new int[]{1, 2}))) - .thenReturn(future1) - .thenReturn(future2); + private void mockEnergyConsumers() { + reset(mConsumedEnergyRetriever); + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER)) + .thenReturn(new int[]{1, 2}); + when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{1, 2}))) + .thenReturn(new long[]{1000, 2000}) + .thenReturn(new long[]{1500, 2700}); } private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) { - CpuPowerStatsCollector.CpuStatsArrayLayout layout = - new CpuPowerStatsCollector.CpuStatsArrayLayout(); + CpuPowerStatsLayout layout = + new CpuPowerStatsLayout(); layout.fromExtras(collector.getPowerStatsDescriptor().extras); return layout.getScalingStepToPowerBracketMap(); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java index cbce7e804de5..70c40f5052f0 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java @@ -28,6 +28,8 @@ import android.os.IBinder; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; +import android.platform.test.ravenwood.RavenwoodRule; import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; @@ -52,11 +54,15 @@ import java.util.regex.Pattern; @RunWith(AndroidJUnit4.class) @LargeTest -@android.platform.test.annotations.IgnoreUnderRavenwood +@android.platform.test.annotations.DisabledOnRavenwood(reason = "Integration test") public class CpuPowerStatsCollectorValidationTest { - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Rule(order = 1) + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() + ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() + : DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int WORK_DURATION_MS = 2000; private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp"; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java index 5c0e26887505..6b5da81954d0 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java @@ -30,6 +30,7 @@ import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SC import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; import android.os.BatteryConsumer; import android.os.PersistableBundle; @@ -55,7 +56,7 @@ import java.util.Map; @RunWith(AndroidJUnit4.class) @SmallTest -public class CpuAggregatedPowerStatsProcessorTest { +public class CpuPowerStatsProcessorTest { @Rule(order = 0) public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() .setProvideMainThread(true) @@ -77,7 +78,7 @@ public class CpuAggregatedPowerStatsProcessorTest { .setCpuPowerBracket(2, 0, 2); private AggregatedPowerStatsConfig.PowerComponent mConfig; - private CpuAggregatedPowerStatsProcessor mProcessor; + private CpuPowerStatsProcessor mProcessor; private MockPowerComponentAggregatedPowerStats mStats; @Before @@ -86,7 +87,7 @@ public class CpuAggregatedPowerStatsProcessorTest { .trackDeviceStates(STATE_POWER, STATE_SCREEN) .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE); - mProcessor = new CpuAggregatedPowerStatsProcessor( + mProcessor = new CpuPowerStatsProcessor( mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies()); } @@ -197,7 +198,7 @@ public class CpuAggregatedPowerStatsProcessorTest { private static class MockPowerComponentAggregatedPowerStats extends PowerComponentAggregatedPowerStats { - private final CpuPowerStatsCollector.CpuStatsArrayLayout mStatsLayout; + private final CpuPowerStatsLayout mStatsLayout; private final PowerStats.Descriptor mDescriptor; private HashMap<String, long[]> mDeviceStats = new HashMap<>(); private HashMap<String, long[]> mUidStats = new HashMap<>(); @@ -207,8 +208,8 @@ public class CpuAggregatedPowerStatsProcessorTest { MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config, boolean useEnergyConsumers) { - super(config); - mStatsLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); + super(new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config); + mStatsLayout = new CpuPowerStatsLayout(); mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3); mStatsLayout.addDeviceSectionCpuTimeByCluster(2); mStatsLayout.addDeviceSectionUsageDuration(); @@ -222,8 +223,8 @@ public class CpuAggregatedPowerStatsProcessorTest { PersistableBundle extras = new PersistableBundle(); mStatsLayout.toExtras(extras); mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, - mStatsLayout.getDeviceStatsArrayLength(), mStatsLayout.getUidStatsArrayLength(), - extras); + mStatsLayout.getDeviceStatsArrayLength(), null, 0, + mStatsLayout.getUidStatsArrayLength(), extras); } @Override diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java index e02386656cb5..f035465dd1df 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java @@ -16,16 +16,26 @@ package com.android.server.power.stats; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.platform.test.ravenwood.RavenwoodRule; import android.system.suspend.internal.WakeLockInfo; import androidx.test.filters.SmallTest; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; import java.nio.charset.Charset; -@android.platform.test.annotations.IgnoreUnderRavenwood -public class KernelWakelockReaderTest extends TestCase { +@android.platform.test.annotations.DisabledOnRavenwood(reason = "Kernel dependency") +public class KernelWakelockReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + /** * Helper class that builds the mock Kernel module file /d/wakeup_sources. */ @@ -105,14 +115,14 @@ public class KernelWakelockReaderTest extends TestCase { private KernelWakelockReader mReader; - @Override + @Before public void setUp() throws Exception { - super.setUp(); mReader = new KernelWakelockReader(); } // ------------------------- Legacy Wakelock Stats Test ------------------------ @SmallTest + @Test public void testParseEmptyFile() throws Exception { KernelWakelockStats staleStats = mReader.parseProcWakelocks(new byte[0], 0, true, new KernelWakelockStats()); @@ -121,6 +131,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testOnlyHeader() throws Exception { byte[] buffer = new ProcFileBuilder().getBytes(); @@ -131,6 +142,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testOneWakelock() throws Exception { byte[] buffer = new ProcFileBuilder() .addLine("Wakelock", 34, 123, 456) // Milliseconds @@ -150,6 +162,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testTwoWakelocks() throws Exception { byte[] buffer = new ProcFileBuilder() .addLine("Wakelock", 1, 10) @@ -166,6 +179,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testDuplicateWakelocksAccumulate() throws Exception { byte[] buffer = new ProcFileBuilder() .addLine("Wakelock", 1, 10) // Milliseconds @@ -184,6 +198,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testWakelocksBecomeStale() throws Exception { KernelWakelockStats staleStats = new KernelWakelockStats(); @@ -209,6 +224,7 @@ public class KernelWakelockReaderTest extends TestCase { // -------------------- SystemSuspend Wakelock Stats Test ------------------- @SmallTest + @Test public void testEmptyWakeLockInfoList() { KernelWakelockStats staleStats = mReader.updateWakelockStats(new WakeLockInfo[0], new KernelWakelockStats()); @@ -217,6 +233,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testOneWakeLockInfo() { WakeLockInfo[] wlStats = new WakeLockInfo[1]; wlStats[0] = createWakeLockInfo("WakeLock", 20, 1000, 500); // Milliseconds @@ -235,6 +252,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testTwoWakeLockInfos() { WakeLockInfo[] wlStats = new WakeLockInfo[2]; wlStats[0] = createWakeLockInfo("WakeLock1", 10, 1000); // Milliseconds @@ -258,6 +276,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testWakeLockInfosBecomeStale() { WakeLockInfo[] wlStats = new WakeLockInfo[1]; wlStats[0] = createWakeLockInfo("WakeLock1", 10, 1000); // Milliseconds @@ -288,6 +307,7 @@ public class KernelWakelockReaderTest extends TestCase { // -------------------- Aggregate Wakelock Stats Tests -------------------- @SmallTest + @Test public void testAggregateStatsEmpty() throws Exception { KernelWakelockStats staleStats = new KernelWakelockStats(); @@ -300,6 +320,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testAggregateStatsNoNativeWakelocks() throws Exception { KernelWakelockStats staleStats = new KernelWakelockStats(); @@ -320,6 +341,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testAggregateStatsNoKernelWakelocks() throws Exception { KernelWakelockStats staleStats = new KernelWakelockStats(); @@ -339,6 +361,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testAggregateStatsBothKernelAndNativeWakelocks() throws Exception { KernelWakelockStats staleStats = new KernelWakelockStats(); @@ -364,6 +387,7 @@ public class KernelWakelockReaderTest extends TestCase { } @SmallTest + @Test public void testAggregateStatsUpdate() throws Exception { KernelWakelockStats staleStats = new KernelWakelockStats(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java index 888a1688c2a1..9b810bc01b4d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java @@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.usage.NetworkStatsManager; import android.net.NetworkCapabilities; import android.net.NetworkStats; @@ -34,6 +35,7 @@ import android.os.BatteryStats; import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; +import android.platform.test.ravenwood.RavenwoodRule; import android.telephony.AccessNetworkConstants; import android.telephony.ActivityStatsTechSpecificInfo; import android.telephony.CellSignalStrength; @@ -46,8 +48,6 @@ import android.telephony.TelephonyManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.frameworks.powerstatstests.R; - import com.google.common.collect.Range; import org.junit.Rule; @@ -56,23 +56,29 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import java.util.ArrayList; +import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest @SuppressWarnings("GuardedBy") public class MobileRadioPowerCalculatorTest { + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + private static final double PRECISION = 0.00001; private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101; @Mock NetworkStatsManager mNetworkStatsManager; - @Rule + @Rule(order = 1) public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); @Test public void testCounterBasedModel() { - mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator) + mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator") .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); @@ -126,10 +132,10 @@ public class MobileRadioPowerCalculatorTest { stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665); // Note application network activity - NetworkStats networkStats = new NetworkStats(10000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100)) - .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0, + NetworkStats networkStats = mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100), + mockNetworkStatsEntry("cellular", APP_UID2, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111)); mStatsRule.setNetworkStats(networkStats); @@ -192,7 +198,7 @@ public class MobileRadioPowerCalculatorTest { @Test public void testCounterBasedModel_multipleDefinedRat() { - mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive) + mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator_multiactive") .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); @@ -246,10 +252,10 @@ public class MobileRadioPowerCalculatorTest { stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665); // Note application network activity - NetworkStats networkStats = new NetworkStats(10000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100)) - .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0, + NetworkStats networkStats = mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100), + mockNetworkStatsEntry("cellular", APP_UID2, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111)); mStatsRule.setNetworkStats(networkStats); @@ -349,7 +355,7 @@ public class MobileRadioPowerCalculatorTest { @Test public void testCounterBasedModel_legacyPowerProfile() { - mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem) + mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem") .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); @@ -403,10 +409,10 @@ public class MobileRadioPowerCalculatorTest { stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665); // Note application network activity - NetworkStats networkStats = new NetworkStats(10000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100)) - .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0, + NetworkStats networkStats = mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100), + mockNetworkStatsEntry("cellular", APP_UID2, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111)); mStatsRule.setNetworkStats(networkStats); @@ -469,7 +475,7 @@ public class MobileRadioPowerCalculatorTest { @Test public void testTimerBasedModel_byProcessState() { - mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem) + mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem") .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID); @@ -521,8 +527,8 @@ public class MobileRadioPowerCalculatorTest { stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665); // Note application network activity - mStatsRule.setNetworkStats(new NetworkStats(10000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, + mStatsRule.setNetworkStats(mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100))); stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000, @@ -531,8 +537,8 @@ public class MobileRadioPowerCalculatorTest { uid.setProcessStateForTest( BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000); - mStatsRule.setNetworkStats(new NetworkStats(12000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, + mStatsRule.setNetworkStats(mockNetworkStats(12000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200))); stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000, @@ -586,7 +592,7 @@ public class MobileRadioPowerCalculatorTest { @Test public void testMeasuredEnergyBasedModel_mobileRadioActiveTimeModel() { - mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem) + mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem") .setPerUidModemModel( BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME) .initMeasuredEnergyStatsLocked(); @@ -619,8 +625,8 @@ public class MobileRadioPowerCalculatorTest { stats.notePhoneOnLocked(9800, 9800); // Note application network activity - NetworkStats networkStats = new NetworkStats(10000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, + NetworkStats networkStats = mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)); mStatsRule.setNetworkStats(networkStats); @@ -662,11 +668,9 @@ public class MobileRadioPowerCalculatorTest { .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION); } - - @Test public void testMeasuredEnergyBasedModel_modemActivityInfoRxTxModel() { - mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive) + mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator_multiactive") .setPerUidModemModel( BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX) .initMeasuredEnergyStatsLocked(); @@ -728,10 +732,10 @@ public class MobileRadioPowerCalculatorTest { stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665); // Note application network activity - NetworkStats networkStats = new NetworkStats(10000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 300, 10, 100)) - .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0, + NetworkStats networkStats = mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 300, 10, 100), + mockNetworkStatsEntry("cellular", APP_UID2, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 2000, 30, 111)); mStatsRule.setNetworkStats(networkStats); @@ -850,7 +854,7 @@ public class MobileRadioPowerCalculatorTest { @Test public void testMeasuredEnergyBasedModel_modemActivityInfoRxTxModel_legacyPowerProfile() { - mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem) + mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem") .setPerUidModemModel( BatteryStatsImpl.PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX) .initMeasuredEnergyStatsLocked(); @@ -908,8 +912,8 @@ public class MobileRadioPowerCalculatorTest { stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665); // Note application network activity - NetworkStats networkStats = new NetworkStats(10000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, + NetworkStats networkStats = mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)); mStatsRule.setNetworkStats(networkStats); @@ -957,7 +961,7 @@ public class MobileRadioPowerCalculatorTest { @Test public void testMeasuredEnergyBasedModel_byProcessState() { - mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem) + mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem") .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID); @@ -988,8 +992,8 @@ public class MobileRadioPowerCalculatorTest { new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); // Note application network activity - mStatsRule.setNetworkStats(new NetworkStats(10000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, + mStatsRule.setNetworkStats(mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100))); stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000, mNetworkStatsManager); @@ -997,8 +1001,8 @@ public class MobileRadioPowerCalculatorTest { uid.setProcessStateForTest( BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000); - mStatsRule.setNetworkStats(new NetworkStats(12000, 1) - .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0, + mStatsRule.setNetworkStats(mockNetworkStats(12000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200))); stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000, mNetworkStatsManager); @@ -1047,4 +1051,40 @@ public class MobileRadioPowerCalculatorTest { final ModemActivityInfo emptyMai = new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L); stats.noteModemControllerActivity(emptyMai, 0, 0, 0, mNetworkStatsManager); } + + private NetworkStats mockNetworkStats(int elapsedTime, int initialSize, + NetworkStats.Entry... entries) { + NetworkStats stats; + if (RavenwoodRule.isOnRavenwood()) { + stats = mock(NetworkStats.class); + when(stats.iterator()).thenAnswer(inv -> List.of(entries).iterator()); + } else { + stats = new NetworkStats(elapsedTime, initialSize); + for (NetworkStats.Entry entry : entries) { + stats = stats.addEntry(entry); + } + } + return stats; + } + + private static NetworkStats.Entry mockNetworkStatsEntry(@Nullable String iface, int uid, + int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes, + long rxPackets, long txBytes, long txPackets, long operations) { + if (RavenwoodRule.isOnRavenwood()) { + NetworkStats.Entry entry = mock(NetworkStats.Entry.class); + when(entry.getUid()).thenReturn(uid); + when(entry.getMetered()).thenReturn(metered); + when(entry.getRoaming()).thenReturn(roaming); + when(entry.getDefaultNetwork()).thenReturn(defaultNetwork); + when(entry.getRxBytes()).thenReturn(rxBytes); + when(entry.getRxPackets()).thenReturn(rxPackets); + when(entry.getTxBytes()).thenReturn(txBytes); + when(entry.getTxPackets()).thenReturn(txPackets); + when(entry.getOperations()).thenReturn(operations); + return entry; + } else { + return new NetworkStats.Entry(iface, uid, set, tag, metered, + roaming, defaultNetwork, rxBytes, rxPackets, txBytes, txPackets, operations); + } + } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java new file mode 100644 index 000000000000..f93c4da3d8d0 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java @@ -0,0 +1,497 @@ +/* + * 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.power.stats; + +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; + +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; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.power.stats.EnergyConsumerType; +import android.net.NetworkStats; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.OutcomeReceiver; +import android.platform.test.ravenwood.RavenwoodRule; +import android.telephony.AccessNetworkConstants; +import android.telephony.ActivityStatsTechSpecificInfo; +import android.telephony.DataConnectionRealTimeInfo; +import android.telephony.ModemActivityInfo; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.IndentingPrintWriter; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.function.IntSupplier; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +public class MobileRadioPowerStatsCollectorTest { + private static final int APP_UID1 = 42; + private static final int APP_UID2 = 24; + private static final int APP_UID3 = 44; + private static final int ISOLATED_UID = 99123; + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = + new BatteryUsageStatsRule().setPowerStatsThrottlePeriodMillis( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, 10000); + + private MockBatteryStatsImpl mBatteryStats; + + private final MockClock mClock = mStatsRule.getMockClock(); + + @Mock + private Context mContext; + @Mock + private PackageManager mPackageManager; + @Mock + private TelephonyManager mTelephony; + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + @Mock + private Supplier<NetworkStats> mNetworkStatsSupplier; + @Mock + private PowerStatsUidResolver mPowerStatsUidResolver; + @Mock + private LongSupplier mCallDurationSupplier; + @Mock + private LongSupplier mScanDurationSupplier; + + private final List<PowerStats> mRecordedPowerStats = new ArrayList<>(); + + private MobileRadioPowerStatsCollector.Injector mInjector = + new MobileRadioPowerStatsCollector.Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> 3500; + } + + @Override + public Supplier<NetworkStats> getMobileNetworkStatsSupplier() { + return mNetworkStatsSupplier; + } + + @Override + public TelephonyManager getTelephonyManager() { + return mTelephony; + } + + @Override + public LongSupplier getCallDurationSupplier() { + return mCallDurationSupplier; + } + + @Override + public LongSupplier getPhoneSignalScanDurationSupplier() { + return mScanDurationSupplier; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true); + when(mPowerStatsUidResolver.mapUid(anyInt())).thenAnswer(invocation -> { + int uid = invocation.getArgument(0); + if (uid == ISOLATED_UID) { + return APP_UID2; + } else { + return uid; + } + }); + mBatteryStats = mStatsRule.getBatteryStats(); + } + + @SuppressWarnings("GuardedBy") + @Test + public void triggering() throws Throwable { + PowerStatsCollector collector = mBatteryStats.getPowerStatsCollector( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO); + collector.addConsumer(mRecordedPowerStats::add); + + mBatteryStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, + true); + + mockModemActivityInfo(1000, 2000, 3000, 600, new int[]{100, 200, 300, 400, 500}); + + // This should trigger a sample collection + mBatteryStats.onSystemReady(mContext); + + mStatsRule.waitForBackgroundThread(); + assertThat(mRecordedPowerStats).hasSize(1); + + mRecordedPowerStats.clear(); + mStatsRule.setTime(20000, 20000); + mBatteryStats.notePhoneOnLocked(mClock.realtime, mClock.uptime); + mStatsRule.waitForBackgroundThread(); + assertThat(mRecordedPowerStats).hasSize(1); + + mRecordedPowerStats.clear(); + mStatsRule.setTime(40000, 40000); + mBatteryStats.notePhoneOffLocked(mClock.realtime, mClock.uptime); + mStatsRule.waitForBackgroundThread(); + assertThat(mRecordedPowerStats).hasSize(1); + + mRecordedPowerStats.clear(); + mStatsRule.setTime(45000, 55000); + mBatteryStats.noteMobileRadioPowerStateLocked( + DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, 0, APP_UID1, mClock.realtime, + mClock.uptime); + mStatsRule.setTime(50001, 50001); + // Elapsed time under the throttling threshold - shouldn't trigger stats collection + mBatteryStats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + 0, APP_UID1, mClock.realtime, mClock.uptime); + mStatsRule.waitForBackgroundThread(); + assertThat(mRecordedPowerStats).hasSize(1); + + mRecordedPowerStats.clear(); + mStatsRule.setTime(50002, 50002); + mBatteryStats.noteMobileRadioPowerStateLocked( + DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, 0, APP_UID1, mClock.realtime, + mClock.uptime); + mStatsRule.setTime(55000, 50000); + // Elapsed time under the throttling threshold - shouldn't trigger stats collection + mBatteryStats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, + 0, APP_UID1, mClock.realtime, mClock.uptime); + mStatsRule.waitForBackgroundThread(); + assertThat(mRecordedPowerStats).isEmpty(); + } + + @Test + public void collectStats() throws Throwable { + PowerStats powerStats = collectPowerStats(true); + assertThat(powerStats.durationMs).isEqualTo(100); + + PowerStats.Descriptor descriptor = powerStats.descriptor; + MobileRadioPowerStatsLayout layout = + new MobileRadioPowerStatsLayout(descriptor); + assertThat(layout.getDeviceSleepTime(powerStats.stats)).isEqualTo(200); + assertThat(layout.getDeviceIdleTime(powerStats.stats)).isEqualTo(300); + assertThat(layout.getDeviceCallTime(powerStats.stats)).isEqualTo(40000); + assertThat(layout.getDeviceScanTime(powerStats.stats)).isEqualTo(60000); + assertThat(layout.getConsumedEnergy(powerStats.stats, 0)) + .isEqualTo((64321 - 10000) * 1000 / 3500); + + assertThat(powerStats.stateStats.size()).isEqualTo(2); + long[] state1 = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey( + BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR, + ServiceState.FREQUENCY_RANGE_MMWAVE + )); + assertThat(layout.getStateRxTime(state1)).isEqualTo(6000); + assertThat(layout.getStateTxTime(state1, 0)).isEqualTo(1000); + assertThat(layout.getStateTxTime(state1, 1)).isEqualTo(2000); + assertThat(layout.getStateTxTime(state1, 2)).isEqualTo(3000); + assertThat(layout.getStateTxTime(state1, 3)).isEqualTo(4000); + assertThat(layout.getStateTxTime(state1, 4)).isEqualTo(5000); + + long[] state2 = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey( + BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + ServiceState.FREQUENCY_RANGE_LOW + )); + assertThat(layout.getStateRxTime(state2)).isEqualTo(7000); + assertThat(layout.getStateTxTime(state2, 0)).isEqualTo(8000); + assertThat(layout.getStateTxTime(state2, 1)).isEqualTo(9000); + assertThat(layout.getStateTxTime(state2, 2)).isEqualTo(1000); + assertThat(layout.getStateTxTime(state2, 3)).isEqualTo(2000); + assertThat(layout.getStateTxTime(state2, 4)).isEqualTo(3000); + + assertThat(powerStats.uidStats.size()).isEqualTo(2); + long[] actual1 = powerStats.uidStats.get(APP_UID1); + assertThat(layout.getUidRxBytes(actual1)).isEqualTo(1000); + assertThat(layout.getUidTxBytes(actual1)).isEqualTo(2000); + assertThat(layout.getUidRxPackets(actual1)).isEqualTo(100); + assertThat(layout.getUidTxPackets(actual1)).isEqualTo(200); + + // Combines APP_UID2 and ISOLATED_UID + long[] actual2 = powerStats.uidStats.get(APP_UID2); + assertThat(layout.getUidRxBytes(actual2)).isEqualTo(6000); + assertThat(layout.getUidTxBytes(actual2)).isEqualTo(3000); + assertThat(layout.getUidRxPackets(actual2)).isEqualTo(60); + assertThat(layout.getUidTxPackets(actual2)).isEqualTo(30); + + assertThat(powerStats.uidStats.get(ISOLATED_UID)).isNull(); + assertThat(powerStats.uidStats.get(APP_UID3)).isNull(); + } + + @Test + public void collectStats_noPerNetworkTypeData() throws Throwable { + PowerStats powerStats = collectPowerStats(false); + assertThat(powerStats.durationMs).isEqualTo(100); + + PowerStats.Descriptor descriptor = powerStats.descriptor; + MobileRadioPowerStatsLayout layout = + new MobileRadioPowerStatsLayout(descriptor); + assertThat(layout.getDeviceSleepTime(powerStats.stats)).isEqualTo(200); + assertThat(layout.getDeviceIdleTime(powerStats.stats)).isEqualTo(300); + assertThat(layout.getConsumedEnergy(powerStats.stats, 0)) + .isEqualTo((64321 - 10000) * 1000 / 3500); + + assertThat(powerStats.stateStats.size()).isEqualTo(1); + long[] stateStats = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey( + AccessNetworkConstants.AccessNetworkType.UNKNOWN, + ServiceState.FREQUENCY_RANGE_UNKNOWN + )); + assertThat(layout.getStateRxTime(stateStats)).isEqualTo(6000); + assertThat(layout.getStateTxTime(stateStats, 0)).isEqualTo(1000); + assertThat(layout.getStateTxTime(stateStats, 1)).isEqualTo(2000); + assertThat(layout.getStateTxTime(stateStats, 2)).isEqualTo(3000); + assertThat(layout.getStateTxTime(stateStats, 3)).isEqualTo(4000); + assertThat(layout.getStateTxTime(stateStats, 4)).isEqualTo(5000); + + assertThat(powerStats.uidStats.size()).isEqualTo(2); + long[] actual1 = powerStats.uidStats.get(APP_UID1); + assertThat(layout.getUidRxBytes(actual1)).isEqualTo(1000); + assertThat(layout.getUidTxBytes(actual1)).isEqualTo(2000); + assertThat(layout.getUidRxPackets(actual1)).isEqualTo(100); + assertThat(layout.getUidTxPackets(actual1)).isEqualTo(200); + + // Combines APP_UID2 and ISOLATED_UID + long[] actual2 = powerStats.uidStats.get(APP_UID2); + assertThat(layout.getUidRxBytes(actual2)).isEqualTo(6000); + assertThat(layout.getUidTxBytes(actual2)).isEqualTo(3000); + assertThat(layout.getUidRxPackets(actual2)).isEqualTo(60); + assertThat(layout.getUidTxPackets(actual2)).isEqualTo(30); + + assertThat(powerStats.uidStats.get(ISOLATED_UID)).isNull(); + assertThat(powerStats.uidStats.get(APP_UID3)).isNull(); + } + + @Test + public void dump() throws Throwable { + PowerStats powerStats = collectPowerStats(true); + StringWriter sw = new StringWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(sw); + powerStats.dump(pw); + pw.flush(); + String dump = sw.toString(); + assertThat(dump).contains("duration=100"); + assertThat(dump).contains( + "stats=[200, 300, 60000, 40000, " + ((64321 - 10000) * 1000 / 3500) + ", 0, 0, 0]"); + assertThat(dump).contains("state LTE: [7000, 8000, 9000, 1000, 2000, 3000]"); + assertThat(dump).contains("state NR MMWAVE: [6000, 1000, 2000, 3000, 4000, 5000]"); + assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 0]"); + assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 0]"); + } + + private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable { + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + collector.setEnabled(true); + + when(mConsumedEnergyRetriever.getEnergyConsumerIds( + EnergyConsumerType.MOBILE_RADIO)).thenReturn(new int[]{777}); + + if (perNetworkTypeData) { + mockModemActivityInfo(1000, 2000, 3000, + AccessNetworkConstants.AccessNetworkType.NGRAN, + ServiceState.FREQUENCY_RANGE_MMWAVE, + 600, new int[]{100, 200, 300, 400, 500}, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + ServiceState.FREQUENCY_RANGE_LOW, + 700, new int[]{800, 900, 100, 200, 300}); + } else { + mockModemActivityInfo(1000, 2000, 3000, 600, new int[]{100, 200, 300, 400, 500}); + } + mockNetworkStats(1000, + 4321, 321, 1234, 23, + 4000, 40, 2000, 20); + + when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777}))) + .thenReturn(new long[]{10000}); + + when(mCallDurationSupplier.getAsLong()).thenReturn(10000L); + when(mScanDurationSupplier.getAsLong()).thenReturn(20000L); + + collector.collectStats(); + + if (perNetworkTypeData) { + mockModemActivityInfo(1100, 2200, 3300, + AccessNetworkConstants.AccessNetworkType.NGRAN, + ServiceState.FREQUENCY_RANGE_MMWAVE, + 6600, new int[]{1100, 2200, 3300, 4400, 5500}, + AccessNetworkConstants.AccessNetworkType.EUTRAN, + ServiceState.FREQUENCY_RANGE_LOW, + 7700, new int[]{8800, 9900, 1100, 2200, 3300}); + } else { + mockModemActivityInfo(1100, 2200, 3300, 6600, new int[]{1100, 2200, 3300, 4400, 5500}); + } + mockNetworkStats(1100, + 5321, 421, 3234, 223, + 8000, 80, 4000, 40); + + when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777}))) + .thenReturn(new long[]{64321}); + when(mCallDurationSupplier.getAsLong()).thenReturn(50000L); + when(mScanDurationSupplier.getAsLong()).thenReturn(80000L); + + mStatsRule.setTime(20000, 20000); + return collector.collectStats(); + } + + private void mockModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs, + int networkType1, int freqRange1, int rxTimeMs1, @NonNull int[] txTimeMs1, + int networkType2, int freqRange2, int rxTimeMs2, @NonNull int[] txTimeMs2) { + ModemActivityInfo info = new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs, + new ActivityStatsTechSpecificInfo[]{ + new ActivityStatsTechSpecificInfo(networkType1, freqRange1, txTimeMs1, + rxTimeMs1), + new ActivityStatsTechSpecificInfo(networkType2, freqRange2, txTimeMs2, + rxTimeMs2)}); + doAnswer(invocation -> { + OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException> + receiver = invocation.getArgument(1); + receiver.onResult(info); + return null; + }).when(mTelephony).requestModemActivityInfo(any(), any()); + } + + private void mockModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs, + int rxTimeMs, @NonNull int[] txTimeMs) { + ModemActivityInfo info = new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs, txTimeMs, + rxTimeMs); + doAnswer(invocation -> { + OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException> + receiver = invocation.getArgument(1); + receiver.onResult(info); + return null; + }).when(mTelephony).requestModemActivityInfo(any(), any()); + } + + private void mockNetworkStats(long elapsedRealtime, + long rxBytes1, long rxPackets1, long txBytes1, long txPackets1, + long rxBytes2, long rxPackets2, long txBytes2, long txPackets2) { + NetworkStats stats; + if (RavenwoodRule.isOnRavenwood()) { + stats = mock(NetworkStats.class); + List<NetworkStats.Entry> entries = List.of( + mockNetworkStatsEntry(APP_UID1, rxBytes1, rxPackets1, txBytes1, txPackets1), + mockNetworkStatsEntry(APP_UID2, rxBytes2, rxPackets2, txBytes2, txPackets2), + mockNetworkStatsEntry(ISOLATED_UID, rxBytes2 / 2, rxPackets2 / 2, txBytes2 / 2, + txPackets2 / 2), + mockNetworkStatsEntry(APP_UID3, 314, 281, 314, 281)); + when(stats.iterator()).thenAnswer(inv -> entries.iterator()); + } else { + stats = new NetworkStats(elapsedRealtime, 1) + .addEntry(new NetworkStats.Entry("mobile", APP_UID1, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes1, rxPackets1, + txBytes1, txPackets1, 100)) + .addEntry(new NetworkStats.Entry("mobile", APP_UID2, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes2, rxPackets2, + txBytes2, txPackets2, 111)) + .addEntry(new NetworkStats.Entry("mobile", ISOLATED_UID, 0, 0, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes2 / 2, rxPackets2 / 2, + txBytes2 / 2, txPackets2 / 2, 111)) + .addEntry(new NetworkStats.Entry("mobile", APP_UID3, 0, 0, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, 314, 281, 314, 281, 111)); + } + when(mNetworkStatsSupplier.get()).thenReturn(stats); + } + + private static NetworkStats.Entry mockNetworkStatsEntry(int uid, long rxBytes, long rxPackets, + long txBytes, long txPackets) { + NetworkStats.Entry entry = mock(NetworkStats.Entry.class); + when(entry.getUid()).thenReturn(uid); + when(entry.getMetered()).thenReturn(METERED_NO); + when(entry.getRoaming()).thenReturn(ROAMING_NO); + when(entry.getDefaultNetwork()).thenReturn(DEFAULT_NETWORK_NO); + when(entry.getRxBytes()).thenReturn(rxBytes); + when(entry.getRxPackets()).thenReturn(rxPackets); + when(entry.getTxBytes()).thenReturn(txBytes); + when(entry.getTxPackets()).thenReturn(txPackets); + when(entry.getOperations()).thenReturn(100L); + return entry; + } + + @Test + public void networkTypeConstants() throws Throwable { + Class<AccessNetworkConstants.AccessNetworkType> clazz = + AccessNetworkConstants.AccessNetworkType.class; + for (Field field : clazz.getDeclaredFields()) { + final int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) + && field.getType().equals(int.class)) { + boolean found = false; + int value = field.getInt(null); + for (int i = 0; i < MobileRadioPowerStatsCollector.NETWORK_TYPES.length; i++) { + if (MobileRadioPowerStatsCollector.NETWORK_TYPES[i] == value) { + found = true; + break; + } + } + assertWithMessage("New network type, " + field.getName() + " not represented in " + + MobileRadioPowerStatsCollector.class).that(found).isTrue(); + } + } + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java new file mode 100644 index 000000000000..4ac7ad8d07ff --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.power.stats; + +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; +import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND; +import static android.os.BatteryConsumer.PROCESS_STATE_CACHED; +import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND; +import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE; + +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.power.stats.EnergyConsumerType; +import android.net.NetworkStats; +import android.os.BatteryConsumer; +import android.os.Handler; +import android.os.OutcomeReceiver; +import android.os.Process; +import android.platform.test.ravenwood.RavenwoodRule; +import android.telephony.ModemActivityInfo; +import android.telephony.TelephonyManager; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.function.IntSupplier; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +public class MobileRadioPowerStatsProcessorTest { + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + private static final double PRECISION = 0.00001; + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101; + private static final int MOBILE_RADIO_ENERGY_CONSUMER_ID = 1; + private static final int VOLTAGE_MV = 3500; + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); + @Mock + private Context mContext; + @Mock + private PowerStatsUidResolver mPowerStatsUidResolver; + @Mock + private PackageManager mPackageManager; + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + @Mock + private Supplier<NetworkStats> mNetworkStatsSupplier; + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private LongSupplier mCallDurationSupplier; + @Mock + private LongSupplier mScanDurationSupplier; + + private final MobileRadioPowerStatsCollector.Injector mInjector = + new MobileRadioPowerStatsCollector.Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> VOLTAGE_MV; + } + + @Override + public Supplier<NetworkStats> getMobileNetworkStatsSupplier() { + return mNetworkStatsSupplier; + } + + @Override + public TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + + @Override + public LongSupplier getCallDurationSupplier() { + return mCallDurationSupplier; + } + + @Override + public LongSupplier getPhoneSignalScanDurationSupplier() { + return mScanDurationSupplier; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true); + when(mPowerStatsUidResolver.mapUid(anyInt())) + .thenAnswer(invocation -> invocation.getArgument(0)); + } + + @Test + public void powerProfileModel() { + // No power monitoring hardware + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO)) + .thenReturn(new int[0]); + + mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator"); + + MobileRadioPowerStatsProcessor processor = + new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile()); + + AggregatedPowerStatsConfig.PowerComponent config = + new AggregatedPowerStatsConfig.PowerComponent( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE) + .setProcessor(processor); + + PowerComponentAggregatedPowerStats aggregatedStats = + new PowerComponentAggregatedPowerStats( + new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config); + + aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0); + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0); + aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); + aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); + + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + collector.setEnabled(true); + + // Initial empty ModemActivityInfo. + mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L)); + + // Establish a baseline + aggregatedStats.addPowerStats(collector.collectStats(), 0); + + // Turn the screen off after 2.5 seconds + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); + aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500); + aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, + 5000); + + // Note application network activity + NetworkStats networkStats = mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100), + mockNetworkStatsEntry("cellular", APP_UID2, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111)); + + when(mNetworkStatsSupplier.get()).thenReturn(networkStats); + + ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, + new int[]{100, 200, 300, 400, 500}, 600); + mockModemActivityInfo(mai); + + when(mCallDurationSupplier.getAsLong()).thenReturn(200L); + when(mScanDurationSupplier.getAsLong()).thenReturn(5555L); + + mStatsRule.setTime(10_000, 10_000); + + PowerStats powerStats = collector.collectStats(); + + aggregatedStats.addPowerStats(powerStats, 10_000); + + processor.finish(aggregatedStats); + + MobileRadioPowerStatsLayout statsLayout = + new MobileRadioPowerStatsLayout( + aggregatedStats.getPowerStatsDescriptor()); + + // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration) + // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration) + // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration) + // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration) + // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration) + // + 1440 mA * 600 ms (RX drain rate * RX duration) + // + 360 mA * 3000 ms (idle drain rate * idle duration) + // + 70 mA * 2000 ms (sleep drain rate * sleep duration) + // _________________ + // = 4604000 mA-ms or 1.27888 mA-h + // 25% of 1.27888 = 0.319722 + // 75% of 1.27888 = 0.959166 + double totalPower = 0; + long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.319722); + totalPower += statsLayout.getDevicePowerEstimate(deviceStats); + + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.959166); + totalPower += statsLayout.getDevicePowerEstimate(deviceStats); + + assertThat(totalPower).isWithin(PRECISION).of(1.27888); + + // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration) + // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration) + // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration) + // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration) + // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration) + // + 1440 mA * 600 ms (RX drain rate * RX duration) + // _________________ + // = 3384000 mA-ms or 0.94 mA-h + double uidPower1 = 0; + long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.17625); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.17625); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.3525); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + double uidPower2 = 0; + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.05875); + uidPower2 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.17625); + uidPower2 += statsLayout.getUidPowerEstimate(uidStats); + + assertThat(uidPower1 + uidPower2) + .isWithin(PRECISION).of(0.94); + + // 3/4 of total packets were sent by APP_UID so 75% of total + assertThat(uidPower1 / (uidPower1 + uidPower2)) + .isWithin(PRECISION).of(0.75); + } + + @Test + public void measuredEnergyModel() { + // PowerStats hardware is available + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO)) + .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID}); + + mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem") + .initMeasuredEnergyStatsLocked(); + + MobileRadioPowerStatsProcessor processor = + new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile()); + + AggregatedPowerStatsConfig.PowerComponent config = + new AggregatedPowerStatsConfig.PowerComponent( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE) + .setProcessor(processor); + + PowerComponentAggregatedPowerStats aggregatedStats = + new PowerComponentAggregatedPowerStats( + new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config); + + aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0); + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0); + aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); + aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); + + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + collector.setEnabled(true); + + // Initial empty ModemActivityInfo. + mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L)); + + when(mConsumedEnergyRetriever.getConsumedEnergyUws( + new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})) + .thenReturn(new long[]{0}); + + // Establish a baseline + aggregatedStats.addPowerStats(collector.collectStats(), 0); + + // Turn the screen off after 2.5 seconds + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); + aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500); + aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, + 5000); + + // Note application network activity + NetworkStats networkStats = mockNetworkStats(10000, 1, + mockNetworkStatsEntry("cellular", APP_UID, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100), + mockNetworkStatsEntry("cellular", APP_UID2, 0, 0, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111)); + + when(mNetworkStatsSupplier.get()).thenReturn(networkStats); + + ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, + new int[]{100, 200, 300, 400, 500}, 600); + mockModemActivityInfo(mai); + + mStatsRule.setTime(10_000, 10_000); + + long energyUws = 10_000_000L * VOLTAGE_MV / 1000L; + when(mConsumedEnergyRetriever.getConsumedEnergyUws( + new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws}); + + when(mCallDurationSupplier.getAsLong()).thenReturn(200L); + when(mScanDurationSupplier.getAsLong()).thenReturn(5555L); + + PowerStats powerStats = collector.collectStats(); + + aggregatedStats.addPowerStats(powerStats, 10_000); + + processor.finish(aggregatedStats); + + MobileRadioPowerStatsLayout statsLayout = + new MobileRadioPowerStatsLayout( + aggregatedStats.getPowerStatsDescriptor()); + + // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh + double totalPower = 0; + long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.671837); + totalPower += statsLayout.getDevicePowerEstimate(deviceStats); + assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.022494); + totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); + + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(2.01596); + totalPower += statsLayout.getDevicePowerEstimate(deviceStats); + assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.067484); + totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); + + // These estimates are supposed to add up to the measured energy, 2.77778 mAh + assertThat(totalPower).isWithin(PRECISION).of(2.77778); + + double uidPower1 = 0; + long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.396473); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + double uidPower2 = 0; + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.066078); + uidPower2 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower2 += statsLayout.getUidPowerEstimate(uidStats); + + // Total power attributed to apps is significantly less than the grand total, + // because we only attribute TX/RX to apps but not maintaining a connection with the cell. + assertThat(uidPower1 + uidPower2) + .isWithin(PRECISION).of(1.057259); + + // 3/4 of total packets were sent by APP_UID so 75% of total RX/TX power is attributed to it + assertThat(uidPower1 / (uidPower1 + uidPower2)) + .isWithin(PRECISION).of(0.75); + } + + private int[] states(int... states) { + return states; + } + + private void mockModemActivityInfo(ModemActivityInfo emptyMai) { + doAnswer(invocation -> { + OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException> + receiver = invocation.getArgument(1); + receiver.onResult(emptyMai); + return null; + }).when(mTelephonyManager).requestModemActivityInfo(any(), any()); + } + + private NetworkStats mockNetworkStats(int elapsedTime, int initialSize, + NetworkStats.Entry... entries) { + NetworkStats stats; + if (RavenwoodRule.isOnRavenwood()) { + stats = mock(NetworkStats.class); + when(stats.iterator()).thenAnswer(inv -> List.of(entries).iterator()); + } else { + stats = new NetworkStats(elapsedTime, initialSize); + for (NetworkStats.Entry entry : entries) { + stats = stats.addEntry(entry); + } + } + return stats; + } + + private static NetworkStats.Entry mockNetworkStatsEntry(@Nullable String iface, int uid, + int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes, + long rxPackets, long txBytes, long txPackets, long operations) { + if (RavenwoodRule.isOnRavenwood()) { + NetworkStats.Entry entry = mock(NetworkStats.Entry.class); + when(entry.getUid()).thenReturn(uid); + when(entry.getMetered()).thenReturn(metered); + when(entry.getRoaming()).thenReturn(roaming); + when(entry.getDefaultNetwork()).thenReturn(defaultNetwork); + when(entry.getRxBytes()).thenReturn(rxBytes); + when(entry.getRxPackets()).thenReturn(rxPackets); + when(entry.getTxBytes()).thenReturn(txBytes); + when(entry.getTxPackets()).thenReturn(txPackets); + when(entry.getOperations()).thenReturn(operations); + return entry; + } else { + return new NetworkStats.Entry(iface, uid, set, tag, metered, + roaming, defaultNetwork, rxBytes, rxPackets, txBytes, txPackets, operations); + } + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 9f069130502f..da3834633552 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -52,6 +52,8 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { // The mNetworkStats will be used for both wifi and mobile categories private NetworkStats mNetworkStats; private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync(); + public static final BatteryStatsConfig DEFAULT_CONFIG = + new BatteryStatsConfig.Builder().build(); MockBatteryStatsImpl() { this(new MockClock()); @@ -66,12 +68,12 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler) { - this(clock, historyDirectory, handler, new PowerStatsUidResolver()); + this(DEFAULT_CONFIG, clock, historyDirectory, handler, new PowerStatsUidResolver()); } - MockBatteryStatsImpl(Clock clock, File historyDirectory, Handler handler, - PowerStatsUidResolver powerStatsUidResolver) { - super(clock, historyDirectory, handler, powerStatsUidResolver, + MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory, + Handler handler, PowerStatsUidResolver powerStatsUidResolver) { + super(config, clock, historyDirectory, handler, powerStatsUidResolver, mock(FrameworkStatsLogger.class), mock(BatteryStatsHistory.TraceDelegate.class), mock(BatteryStatsHistory.EventLogger.class)); initTimersAndCounters(); @@ -276,10 +278,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { public void writeSyncLocked() { } - public void setHandler(Handler handler) { - mHandler = handler; - } - @Override protected void updateBatteryPropertiesLocked() { } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java new file mode 100644 index 000000000000..dadcf3f3871e --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.power.stats; + +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.power.stats.EnergyConsumerType; +import android.net.NetworkStats; +import android.os.BatteryConsumer; +import android.os.Handler; +import android.os.OutcomeReceiver; +import android.platform.test.ravenwood.RavenwoodRule; +import android.telephony.ModemActivityInfo; +import android.telephony.TelephonyManager; + +import com.android.internal.os.Clock; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.IntSupplier; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +public class PhoneCallPowerStatsProcessorTest { + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + private static final double PRECISION = 0.00001; + private static final int VOLTAGE_MV = 3500; + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); + @Mock + private Context mContext; + @Mock + private PowerStatsUidResolver mPowerStatsUidResolver; + @Mock + private PackageManager mPackageManager; + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + @Mock + private Supplier<NetworkStats> mNetworkStatsSupplier; + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private LongSupplier mCallDurationSupplier; + @Mock + private LongSupplier mScanDurationSupplier; + + private final MobileRadioPowerStatsCollector.Injector mInjector = + new MobileRadioPowerStatsCollector.Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> VOLTAGE_MV; + } + + @Override + public Supplier<NetworkStats> getMobileNetworkStatsSupplier() { + return mNetworkStatsSupplier; + } + + @Override + public TelephonyManager getTelephonyManager() { + return mTelephonyManager; + } + + @Override + public LongSupplier getCallDurationSupplier() { + return mCallDurationSupplier; + } + + @Override + public LongSupplier getPhoneSignalScanDurationSupplier() { + return mScanDurationSupplier; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true); + when(mPowerStatsUidResolver.mapUid(anyInt())) + .thenAnswer(invocation -> invocation.getArgument(0)); + + // No power monitoring hardware + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO)) + .thenReturn(new int[0]); + + mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem"); + } + + @Test + public void copyEstimatesFromMobileRadioPowerStats() { + MobileRadioPowerStatsProcessor mobileStatsProcessor = + new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile()); + + PhoneCallPowerStatsProcessor phoneStatsProcessor = + new PhoneCallPowerStatsProcessor(); + + AggregatedPowerStatsConfig aggregatedPowerStatsConfig = new AggregatedPowerStatsConfig(); + aggregatedPowerStatsConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE) + .setProcessor(mobileStatsProcessor); + aggregatedPowerStatsConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE, + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) + .setProcessor(phoneStatsProcessor); + + AggregatedPowerStats aggregatedPowerStats = + new AggregatedPowerStats(aggregatedPowerStatsConfig); + PowerComponentAggregatedPowerStats mobileRadioStats = + aggregatedPowerStats.getPowerComponentStats( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO); + + aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0); + aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0); + + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + collector.setEnabled(true); + + // Initial empty ModemActivityInfo. + mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L)); + + // Establish a baseline + aggregatedPowerStats.addPowerStats(collector.collectStats(), 0); + + // Turn the screen off after 2.5 seconds + aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); + + ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, + new int[]{100, 200, 300, 400, 500}, 600); + mockModemActivityInfo(mai); + + // A phone call was made + when(mCallDurationSupplier.getAsLong()).thenReturn(7000L); + + mStatsRule.setTime(10_000, 10_000); + + aggregatedPowerStats.addPowerStats(collector.collectStats(), 10_000); + + mobileStatsProcessor.finish(mobileRadioStats); + + PowerComponentAggregatedPowerStats stats = + aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_PHONE); + phoneStatsProcessor.finish(stats); + + PowerStatsLayout statsLayout = + new PowerStatsLayout(stats.getPowerStatsDescriptor()); + + long[] deviceStats = new long[stats.getPowerStatsDescriptor().statsArrayLength]; + stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.7); + stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(2.1); + } + + private void mockModemActivityInfo(ModemActivityInfo emptyMai) { + doAnswer(invocation -> { + OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException> + receiver = invocation.getArgument(1); + receiver.onResult(emptyMai); + return null; + }).when(mTelephonyManager).requestModemActivityInfo(any(), any()); + } + + private int[] states(int... states) { + return states; + } +} 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 2ea86a4527eb..03b02cfde146 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 @@ -76,9 +76,8 @@ public class PowerStatsAggregatorTest { @Test public void stateUpdates() { - PowerStats.Descriptor descriptor = - new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, - new PersistableBundle()); + PowerStats.Descriptor descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, + "majorDrain", 1, null, 0, 1, new PersistableBundle()); PowerStats powerStats = new PowerStats(descriptor); mClock.currentTime = 1222156800000L; // An important date in world history @@ -186,9 +185,8 @@ public class PowerStatsAggregatorTest { @Test public void incompatiblePowerStats() { - PowerStats.Descriptor descriptor = - new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, - new PersistableBundle()); + PowerStats.Descriptor descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, + "majorDrain", 1, null, 0, 1, new PersistableBundle()); PowerStats powerStats = new PowerStats(descriptor); mHistory.forceRecordAllHistory(); @@ -209,7 +207,7 @@ public class PowerStatsAggregatorTest { advance(1000); - descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, 1, + descriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "majorDrain", 1, null, 0, 1, PersistableBundle.forPair("something", "changed")); powerStats = new PowerStats(descriptor); powerStats.stats = new long[]{20000}; 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 17a7d3ecf9d3..df1200bb6b1a 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 @@ -18,11 +18,22 @@ package com.android.server.power.stats; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.PersistableBundle; +import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.ravenwood.RavenwoodRule; +import android.power.PowerStatsInternal; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -34,6 +45,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @SmallTest public class PowerStatsCollectorTest { @@ -57,7 +71,8 @@ public class PowerStatsCollectorTest { mMockClock) { @Override protected PowerStats collectStats() { - return new PowerStats(new PowerStats.Descriptor(0, 0, 0, new PersistableBundle())); + return new PowerStats( + new PowerStats.Descriptor(0, 0, null, 0, 0, new PersistableBundle())); } }; mCollector.addConsumer(stats -> mCollectedStats = stats); @@ -92,4 +107,74 @@ public class PowerStatsCollectorTest { mHandler.post(done::open); done.block(); } + + @Test + @DisabledOnRavenwood + public void consumedEnergyRetriever() throws Exception { + PowerStatsInternal powerStatsInternal = mock(PowerStatsInternal.class); + mockEnergyConsumers(powerStatsInternal); + + PowerStatsCollector.ConsumedEnergyRetrieverImpl retriever = + new PowerStatsCollector.ConsumedEnergyRetrieverImpl(powerStatsInternal); + int[] energyConsumerIds = retriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER); + assertThat(energyConsumerIds).isEqualTo(new int[]{1, 2}); + long[] energy = retriever.getConsumedEnergyUws(energyConsumerIds); + assertThat(energy).isEqualTo(new long[]{1000, 2000}); + energy = retriever.getConsumedEnergyUws(energyConsumerIds); + assertThat(energy).isEqualTo(new long[]{1500, 2700}); + } + + @SuppressWarnings("unchecked") + private void mockEnergyConsumers(PowerStatsInternal powerStatsInternal) throws Exception { + when(powerStatsInternal.getEnergyConsumerInfo()) + .thenReturn(new EnergyConsumer[]{ + new EnergyConsumer() {{ + id = 1; + type = EnergyConsumerType.CPU_CLUSTER; + ordinal = 0; + name = "CPU0"; + }}, + new EnergyConsumer() {{ + id = 2; + type = EnergyConsumerType.CPU_CLUSTER; + ordinal = 1; + name = "CPU4"; + }}, + new EnergyConsumer() {{ + id = 3; + type = EnergyConsumerType.BLUETOOTH; + name = "BT"; + }}, + }); + + CompletableFuture<EnergyConsumerResult[]> future1 = mock(CompletableFuture.class); + when(future1.get(anyLong(), any(TimeUnit.class))) + .thenReturn(new EnergyConsumerResult[]{ + new EnergyConsumerResult() {{ + id = 1; + energyUWs = 1000; + }}, + new EnergyConsumerResult() {{ + id = 2; + energyUWs = 2000; + }} + }); + + CompletableFuture<EnergyConsumerResult[]> future2 = mock(CompletableFuture.class); + when(future2.get(anyLong(), any(TimeUnit.class))) + .thenReturn(new EnergyConsumerResult[]{ + new EnergyConsumerResult() {{ + id = 1; + energyUWs = 1500; + }}, + new EnergyConsumerResult() {{ + id = 2; + energyUWs = 2700; + }} + }); + + when(powerStatsInternal.getEnergyConsumedAsync(eq(new int[]{1, 2}))) + .thenReturn(future1) + .thenReturn(future2); + } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java index 18d7b909150b..412fc88dcd27 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java @@ -77,7 +77,7 @@ public class PowerStatsExporterTest { private PowerStatsStore mPowerStatsStore; private PowerStatsAggregator mPowerStatsAggregator; private BatteryStatsHistory mHistory; - private CpuPowerStatsCollector.CpuStatsArrayLayout mCpuStatsArrayLayout; + private CpuPowerStatsLayout mCpuStatsArrayLayout; private PowerStats.Descriptor mPowerStatsDescriptor; @Before @@ -93,7 +93,7 @@ public class PowerStatsExporterTest { AggregatedPowerStatsConfig.STATE_SCREEN, AggregatedPowerStatsConfig.STATE_PROCESS_STATE) .setProcessor( - new CpuAggregatedPowerStatsProcessor(mStatsRule.getPowerProfile(), + new CpuPowerStatsProcessor(mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies())); mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler(), config); @@ -102,9 +102,10 @@ public class PowerStatsExporterTest { mMonotonicClock, null, null); mPowerStatsAggregator = new PowerStatsAggregator(config, mHistory); - mCpuStatsArrayLayout = new CpuPowerStatsCollector.CpuStatsArrayLayout(); + mCpuStatsArrayLayout = new CpuPowerStatsLayout(); mCpuStatsArrayLayout.addDeviceSectionCpuTimeByScalingStep(1); mCpuStatsArrayLayout.addDeviceSectionCpuTimeByCluster(1); + mCpuStatsArrayLayout.addDeviceSectionUsageDuration(); mCpuStatsArrayLayout.addDeviceSectionPowerEstimate(); mCpuStatsArrayLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0}); mCpuStatsArrayLayout.addUidSectionPowerEstimate(); @@ -113,7 +114,7 @@ public class PowerStatsExporterTest { mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, mCpuStatsArrayLayout.getDeviceStatsArrayLength(), - mCpuStatsArrayLayout.getUidStatsArrayLength(), extras); + null, 0, mCpuStatsArrayLayout.getUidStatsArrayLength(), extras); } @Test @@ -126,20 +127,20 @@ public class PowerStatsExporterTest { BatteryUsageStats actual = builder.build(); String message = "Actual BatteryUsageStats: " + actual; - assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); - assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_ANY, 13.5); + BatteryConsumer.PROCESS_STATE_ANY, 3.97099); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_FOREGROUND, 7.47); + BatteryConsumer.PROCESS_STATE_FOREGROUND, 2.198082); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_BACKGROUND, 6.03); + BatteryConsumer.PROCESS_STATE_BACKGROUND, 1.772916); assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_ANY, 12.03); + BatteryConsumer.PROCESS_STATE_ANY, 3.538999); assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 12.03); + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 3.538999); actual.close(); } @@ -154,20 +155,20 @@ public class PowerStatsExporterTest { BatteryUsageStats actual = builder.build(); String message = "Actual BatteryUsageStats: " + actual; - assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4); - assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 15.4); + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 4.526749); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 4.526749); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_ANY, 4.06); + BatteryConsumer.PROCESS_STATE_ANY, 1.193332); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_FOREGROUND, 1.35); + BatteryConsumer.PROCESS_STATE_FOREGROUND, 0.397749); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_BACKGROUND, 2.70); + BatteryConsumer.PROCESS_STATE_BACKGROUND, 0.795583); assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_ANY, 11.33); + BatteryConsumer.PROCESS_STATE_ANY, 3.333249); assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 11.33); + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, 3.333249); actual.close(); } @@ -182,13 +183,13 @@ public class PowerStatsExporterTest { BatteryUsageStats actual = builder.build(); String message = "Actual BatteryUsageStats: " + actual; - assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); - assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 25.53); + assertDevicePowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); + assertAllAppsPowerEstimate(message, actual, BatteryConsumer.POWER_COMPONENT_CPU, 7.51016); assertUidPowerEstimate(message, actual, APP_UID1, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_ANY, 13.5); + BatteryConsumer.PROCESS_STATE_ANY, 3.97099); assertUidPowerEstimate(message, actual, APP_UID2, BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_ANY, 12.03); + BatteryConsumer.PROCESS_STATE_ANY, 3.538999); UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream() .filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null); // There shouldn't be any per-procstate data diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsProcessorTest.java index af83be04db7d..02e446aa1859 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsProcessorTest.java @@ -35,7 +35,7 @@ import java.util.Objects; @RunWith(AndroidJUnit4.class) @SmallTest -public class AggregatedPowerStatsProcessorTest { +public class PowerStatsProcessorTest { @Test public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() { @@ -44,8 +44,8 @@ public class AggregatedPowerStatsProcessorTest { .trackDeviceStates(STATE_POWER, STATE_SCREEN) .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE); - AggregatedPowerStatsProcessor.PowerEstimationPlan plan = - new AggregatedPowerStatsProcessor.PowerEstimationPlan(config); + PowerStatsProcessor.PowerEstimationPlan plan = + new PowerStatsProcessor.PowerEstimationPlan(config); assertThat(deviceStateEstimatesToStrings(plan)) .containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]"); assertThat(combinedDeviceStatsToStrings(plan)) @@ -65,8 +65,8 @@ public class AggregatedPowerStatsProcessorTest { .trackDeviceStates(STATE_POWER, STATE_SCREEN) .trackUidStates(STATE_POWER, STATE_PROCESS_STATE); - AggregatedPowerStatsProcessor.PowerEstimationPlan plan = - new AggregatedPowerStatsProcessor.PowerEstimationPlan(config); + PowerStatsProcessor.PowerEstimationPlan plan = + new PowerStatsProcessor.PowerEstimationPlan(config); assertThat(deviceStateEstimatesToStrings(plan)) .containsExactly("[0, 0]", "[0, 1]", "[1, 0]", "[1, 1]"); @@ -81,13 +81,13 @@ public class AggregatedPowerStatsProcessorTest { } private static List<String> deviceStateEstimatesToStrings( - AggregatedPowerStatsProcessor.PowerEstimationPlan plan) { + PowerStatsProcessor.PowerEstimationPlan plan) { return plan.deviceStateEstimations.stream() .map(dse -> dse.stateValues).map(Arrays::toString).toList(); } private static List<String> combinedDeviceStatsToStrings( - AggregatedPowerStatsProcessor.PowerEstimationPlan plan) { + PowerStatsProcessor.PowerEstimationPlan plan) { return plan.combinedDeviceStateEstimations.stream() .map(cds -> cds.deviceStateEstimations) .map(dses -> dses.stream() @@ -97,7 +97,7 @@ public class AggregatedPowerStatsProcessorTest { } private static List<String> uidStateEstimatesToStrings( - AggregatedPowerStatsProcessor.PowerEstimationPlan plan, + PowerStatsProcessor.PowerEstimationPlan plan, AggregatedPowerStatsConfig.PowerComponent config) { MultiStateStats.States[] uidStateConfig = config.getUidStateConfig(); return plan.uidStateEstimates.stream() diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java index 80cbe0da402e..d67d40862e95 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java @@ -18,11 +18,14 @@ package com.android.server.power.stats; import static com.google.common.truth.Truth.assertThat; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.KernelSingleProcessCpuThreadReader; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,7 +33,10 @@ import java.io.IOException; @SmallTest @RunWith(AndroidJUnit4.class) +@android.platform.test.annotations.DisabledOnRavenwood(reason = "Kernel dependency") public class SystemServerCpuThreadReaderTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); @Test public void testReadDelta() throws IOException { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java index 8e53d5285cc4..ef0b570a1354 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java @@ -31,9 +31,10 @@ import android.os.Process; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.RavenwoodFlagsValueProvider; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.KernelCpuSpeedReader; @@ -46,7 +47,6 @@ import com.android.server.power.optimization.Flags; 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; @@ -55,17 +55,21 @@ import java.util.ArrayList; import java.util.Collection; @SmallTest -@RunWith(AndroidJUnit4.class) @SuppressWarnings("GuardedBy") public class SystemServicePowerCalculatorTest { - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Rule(order = 1) + public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() + ? RavenwoodFlagsValueProvider.createAllOnCheckFlagsRule() + : DeviceFlagsValueProvider.createCheckFlagsRule(); private static final double PRECISION = 0.000001; private static final int APP_UID1 = 100; private static final int APP_UID2 = 200; - @Rule + @Rule(order = 2) public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720) .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200}) 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 4405a20cb059..b91ef7cbfff8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -1256,6 +1256,30 @@ public final class UserManagerTest { @MediumTest @Test + public void testDefaultUserRestrictionsForPrivateProfile() { + assumeTrue(mUserManager.canAddPrivateProfile()); + final int currentUserId = ActivityManager.getCurrentUser(); + UserInfo privateProfileInfo = null; + try { + privateProfileInfo = createProfileForUser("Private", + UserManager.USER_TYPE_PROFILE_PRIVATE, currentUserId); + assertThat(privateProfileInfo).isNotNull(); + } catch (Exception e) { + fail("Creation of private profile failed due to " + e.getMessage()); + } + assertDefaultPrivateProfileRestrictions(privateProfileInfo.getUserHandle()); + } + + private void assertDefaultPrivateProfileRestrictions(UserHandle userHandle) { + Bundle defaultPrivateProfileRestrictions = + UserTypeFactory.getDefaultPrivateProfileRestrictions(); + for (String restriction : defaultPrivateProfileRestrictions.keySet()) { + assertThat(mUserManager.hasUserRestrictionForUser(restriction, userHandle)).isTrue(); + } + } + + @MediumTest + @Test public void testAddRestrictedProfile() throws Exception { if (isAutomotive() || UserManager.isHeadlessSystemUserMode()) return; assertWithMessage("There should be no associated restricted profiles before the test") 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 610627886c1b..f08fbde962ef 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -7916,8 +7916,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, true); // enqueue toast -> toast should still enqueue - enqueueToast(testPackage, new TestableToastCallback()); + boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback()); assertEquals(1, mService.mToastQueue.size()); + assertThat(wasEnqueued).isTrue(); } @Test @@ -7936,8 +7937,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, false); // enqueue toast -> no toasts enqueued - enqueueToast(testPackage, new TestableToastCallback()); + boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback()); assertEquals(0, mService.mToastQueue.size()); + assertThat(wasEnqueued).isFalse(); } @Test @@ -8045,8 +8047,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, true); // enqueue toast -> toast should still enqueue - enqueueTextToast(testPackage, "Text"); + boolean wasEnqueued = enqueueTextToast(testPackage, "Text"); assertEquals(1, mService.mToastQueue.size()); + assertThat(wasEnqueued).isTrue(); } @Test @@ -8065,8 +8068,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, false); // enqueue toast -> toast should still enqueue - enqueueTextToast(testPackage, "Text"); + boolean wasEnqueued = enqueueTextToast(testPackage, "Text"); assertEquals(1, mService.mToastQueue.size()); + assertThat(wasEnqueued).isTrue(); } @Test @@ -8220,8 +8224,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, false); // enqueue toast -> toast should still enqueue - enqueueToast(testPackage, new TestableToastCallback()); + boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback()); assertEquals(1, mService.mToastQueue.size()); + assertThat(wasEnqueued).isTrue(); verify(mAm).setProcessImportant(any(), anyInt(), eq(true), any()); } @@ -8242,8 +8247,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, true); // enqueue toast -> toast should still enqueue - enqueueTextToast(testPackage, "Text"); + boolean wasEnqueued = enqueueTextToast(testPackage, "Text"); assertEquals(1, mService.mToastQueue.size()); + assertThat(wasEnqueued).isTrue(); verify(mAm).setProcessImportant(any(), anyInt(), eq(false), any()); } @@ -8264,8 +8270,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, false); // enqueue toast -> toast should still enqueue - enqueueTextToast(testPackage, "Text"); + boolean wasEnqueued = enqueueTextToast(testPackage, "Text"); assertEquals(1, mService.mToastQueue.size()); + assertThat(wasEnqueued).isTrue(); verify(mAm).setProcessImportant(any(), anyInt(), eq(false), any()); } @@ -8274,7 +8281,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { allowTestPackageToToast(); // enqueue toast -> no toasts enqueued - enqueueTextToast(TEST_PACKAGE, "Text"); + boolean wasEnqueued = enqueueTextToast(TEST_PACKAGE, "Text"); + assertThat(wasEnqueued).isTrue(); verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY); } @@ -8367,10 +8375,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(false); // enqueue toast -> no toasts enqueued - enqueueTextToast(testPackage, "Text"); + boolean wasEnqueued = enqueueTextToast(testPackage, "Text"); verify(mStatusBar, never()).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(), anyInt()); assertEquals(0, mService.mToastQueue.size()); + assertThat(wasEnqueued).isFalse(); } @Test @@ -8390,10 +8399,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); // enqueue toast -> no toasts enqueued - enqueueToast(testPackage, new TestableToastCallback()); + boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback()); verify(mStatusBar, never()).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(), anyInt()); assertEquals(0, mService.mToastQueue.size()); + assertThat(wasEnqueued).isFalse(); } @Test @@ -8415,8 +8425,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, false); // enqueue toast -> no toasts enqueued - enqueueToast(testPackage, new TestableToastCallback()); + boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback()); assertEquals(0, mService.mToastQueue.size()); + assertThat(wasEnqueued).isFalse(); } @Test @@ -8437,8 +8448,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setAppInForegroundForToasts(mUid, false); // enqueue toast -> system toast can still be enqueued - enqueueToast(testPackage, new TestableToastCallback()); + boolean wasEnqueued = enqueueToast(testPackage, new TestableToastCallback()); assertEquals(1, mService.mToastQueue.size()); + assertThat(wasEnqueued).isTrue(); } @Test @@ -8458,7 +8470,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Trying to quickly enqueue more toast than allowed. for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS + 1; i++) { - enqueueTextToast(testPackage, "Text"); + boolean wasEnqueued = enqueueTextToast(testPackage, "Text"); + if (i < NotificationManagerService.MAX_PACKAGE_TOASTS) { + assertThat(wasEnqueued).isTrue(); + } else { + assertThat(wasEnqueued).isFalse(); + } } // Only allowed number enqueued, rest ignored. assertEquals(NotificationManagerService.MAX_PACKAGE_TOASTS, mService.mToastQueue.size()); @@ -15089,25 +15106,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(false); } - private void enqueueToast(String testPackage, ITransientNotification callback) + private boolean enqueueToast(String testPackage, ITransientNotification callback) throws RemoteException { - enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback); + return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), + callback); } - private void enqueueToast(INotificationManager service, String testPackage, + private boolean enqueueToast(INotificationManager service, String testPackage, IBinder token, ITransientNotification callback) throws RemoteException { - service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ true, - DEFAULT_DISPLAY); + return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ + true, DEFAULT_DISPLAY); } - private void enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { - enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); + private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { + return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); } - private void enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, + private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, int displayId) throws RemoteException { - ((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text, - TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null); + return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, + new Binder(), text, TOAST_DURATION, isUiContext, displayId, + /* textCallback= */ null); } private void mockIsVisibleBackgroundUsersSupported(boolean supported) { diff --git a/services/tests/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml index a14ea5598758..c0f514fb9673 100644 --- a/services/tests/vibrator/AndroidManifest.xml +++ b/services/tests/vibrator/AndroidManifest.xml @@ -30,6 +30,8 @@ <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" /> <!-- Required to set always-on vibrations --> <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" /> + <!-- Required to play system-only haptic feedback constants --> + <uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" /> <application android:debuggable="true"> <uses-library android:name="android.test.mock" android:required="true" /> diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java index e3d45967848a..633a3c985b7f 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -27,6 +27,8 @@ import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK; import static android.os.VibrationEffect.EFFECT_CLICK; import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK; import static android.os.VibrationEffect.EFFECT_TICK; +import static android.view.HapticFeedbackConstants.BIOMETRIC_CONFIRM; +import static android.view.HapticFeedbackConstants.BIOMETRIC_REJECT; import static android.view.HapticFeedbackConstants.CLOCK_TICK; import static android.view.HapticFeedbackConstants.CONTEXT_CLICK; import static android.view.HapticFeedbackConstants.KEYBOARD_RELEASE; @@ -80,6 +82,8 @@ public class HapticFeedbackVibrationProviderTest { new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}; private static final int[] KEYBOARD_FEEDBACK_CONSTANTS = new int[] {KEYBOARD_TAP, KEYBOARD_RELEASE}; + private static final int[] BIOMETRIC_FEEDBACK_CONSTANTS = + new int[] {BIOMETRIC_CONFIRM, BIOMETRIC_REJECT}; private static final float KEYBOARD_VIBRATION_FIXED_AMPLITUDE = 0.62f; @@ -283,6 +287,17 @@ public class HapticFeedbackVibrationProviderTest { } @Test + public void testVibrationAttribute_biometricConstants_returnsCommunicationRequestUsage() { + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) { + VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback( + effectId, /* bypassVibrationIntensitySetting= */ false, /* fromIme= */ false); + assertThat(attrs.getUsage()).isEqualTo(VibrationAttributes.USAGE_COMMUNICATION_REQUEST); + } + } + + @Test public void testVibrationAttribute_forNotBypassingIntensitySettings() { HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); @@ -422,6 +437,15 @@ public class HapticFeedbackVibrationProviderTest { } } + @Test + public void testIsRestricted_biometricConstants_returnsTrue() { + HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations(); + + for (int effectId : BIOMETRIC_FEEDBACK_CONSTANTS) { + assertThat(hapticProvider.isRestrictedHapticFeedback(effectId)).isTrue(); + } + } + private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() { return createProvider(/* customizations= */ null); } 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 185677f966a4..d6c0fef9649a 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -1410,6 +1410,70 @@ public class VibratorManagerServiceTest { } @Test + public void performHapticFeedback_restrictedConstantsWithoutPermission_doesNotVibrate() + throws Exception { + // Deny permission to vibrate with restricted constants + denyPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS); + // Public constant, no permission required + mHapticFeedbackVibrationMap.put( + HapticFeedbackConstants.CONFIRM, + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); + // Hidden system-only constant, permission required + mHapticFeedbackVibrationMap.put( + HapticFeedbackConstants.BIOMETRIC_CONFIRM, + VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK)); + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setSupportedEffects( + VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK); + VibratorManagerService service = createSystemReadyService(); + + performHapticFeedbackAndWaitUntilFinished( + service, HapticFeedbackConstants.CONFIRM, /* always= */ false); + + performHapticFeedbackAndWaitUntilFinished( + service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* always= */ false); + + List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments(); + assertEquals(1, playedSegments.size()); + PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0); + assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId()); + } + + @Test + public void performHapticFeedback_restrictedConstantsWithPermission_playsVibration() + throws Exception { + // Grant permission to vibrate with restricted constants + grantPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS); + // Public constant, no permission required + mHapticFeedbackVibrationMap.put( + HapticFeedbackConstants.CONFIRM, + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); + // Hidden system-only constant, permission required + mHapticFeedbackVibrationMap.put( + HapticFeedbackConstants.BIOMETRIC_CONFIRM, + VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK)); + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setSupportedEffects( + VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_HEAVY_CLICK); + VibratorManagerService service = createSystemReadyService(); + + performHapticFeedbackAndWaitUntilFinished( + service, HapticFeedbackConstants.CONFIRM, /* always= */ false); + + performHapticFeedbackAndWaitUntilFinished( + service, HapticFeedbackConstants.BIOMETRIC_CONFIRM, /* always= */ false); + + List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments(); + assertEquals(2, playedSegments.size()); + assertEquals(VibrationEffect.EFFECT_CLICK, + ((PrebakedSegment) playedSegments.get(0)).getEffectId()); + assertEquals(VibrationEffect.EFFECT_HEAVY_CLICK, + ((PrebakedSegment) playedSegments.get(1)).getEffectId()); + } + + @Test public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception { denyPermission(android.Manifest.permission.VIBRATE); mHapticFeedbackVibrationMap.put( diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 856ad2a02444..fbf142632c78 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -130,6 +130,7 @@ import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; @@ -2363,6 +2364,92 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testUserOverrideFullscreenForLandscapeDisplay() { + final int displayWidth = 1600; + final int displayHeight = 1400; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + spyOn(mActivity.mWmService.mLetterboxConfiguration); + doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration) + .isUserAppAspectRatioFullscreenEnabled(); + + // Set user aspect ratio override + spyOn(mActivity.mLetterboxUiController); + doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController) + .getUserMinAspectRatioOverrideCode(); + + prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT); + + final Rect bounds = mActivity.getBounds(); + + // bounds should be fullscreen + assertEquals(displayHeight, bounds.height()); + assertEquals(displayWidth, bounds.width()); + } + + @Test + public void testUserOverrideFullscreenForPortraitDisplay() { + final int displayWidth = 1400; + final int displayHeight = 1600; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + spyOn(mActivity.mWmService.mLetterboxConfiguration); + doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration) + .isUserAppAspectRatioFullscreenEnabled(); + + // Set user aspect ratio override + spyOn(mActivity.mLetterboxUiController); + doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController) + .getUserMinAspectRatioOverrideCode(); + + prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE); + + final Rect bounds = mActivity.getBounds(); + + // bounds should be fullscreen + assertEquals(displayHeight, bounds.height()); + assertEquals(displayWidth, bounds.width()); + } + + @Test + public void testSystemFullscreenOverrideForLandscapeDisplay() { + final int displayWidth = 1600; + final int displayHeight = 1400; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + spyOn(mActivity.mLetterboxUiController); + doReturn(true).when(mActivity.mLetterboxUiController) + .isSystemOverrideToFullscreenEnabled(); + + prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT); + + final Rect bounds = mActivity.getBounds(); + + // bounds should be fullscreen + assertEquals(displayHeight, bounds.height()); + assertEquals(displayWidth, bounds.width()); + } + + @Test + public void testSystemFullscreenOverrideForPortraitDisplay() { + final int displayWidth = 1400; + final int displayHeight = 1600; + setUpDisplaySizeWithApp(displayWidth, displayHeight); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + spyOn(mActivity.mLetterboxUiController); + doReturn(true).when(mActivity.mLetterboxUiController) + .isSystemOverrideToFullscreenEnabled(); + + prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE); + + final Rect bounds = mActivity.getBounds(); + + // bounds should be fullscreen + assertEquals(displayHeight, bounds.height()); + assertEquals(displayWidth, bounds.width()); + } + + @Test public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() { final int displayWidth = 1600; final int displayHeight = 1400; @@ -4117,6 +4204,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @Ignore // TODO(b/330888878): fix test in main public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() { if (Flags.insetsDecoupledConfiguration()) { // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index ae4faa84421e..9729c688439e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -180,7 +180,8 @@ public class VoiceInteractionManagerService extends SystemService { LocalServices.getService(ActivityManagerInternal.class)); mAtmInternal = Objects.requireNonNull( LocalServices.getService(ActivityTaskManagerInternal.class)); - mWmInternal = LocalServices.getService(WindowManagerInternal.class); + mWmInternal = Objects.requireNonNull( + LocalServices.getService(WindowManagerInternal.class)); mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService( LegacyPermissionManagerInternal.class); @@ -2737,12 +2738,8 @@ public class VoiceInteractionManagerService extends SystemService { isManagedProfileVisible = true; } } - final ScreenCapture.ScreenshotHardwareBuffer shb; - if (mWmInternal != null) { - shb = mWmInternal.takeAssistScreenshot(); - } else { - shb = null; - } + final ScreenCapture.ScreenshotHardwareBuffer shb = + mWmInternal.takeAssistScreenshot(); final Bitmap bm = shb != null ? shb.asBitmap() : null; // Now that everything is fetched, putting it in the launchIntent. if (bm != null) { diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index ec60c676d078..0ddc38a78b1e 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -35,8 +35,6 @@ import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.flags.FeatureFlags; -import com.android.internal.telephony.flags.FeatureFlagsImpl; import java.util.HashMap; import java.util.HashSet; @@ -48,8 +46,7 @@ public final class TelephonyPermissions { private static final String LOG_TAG = "TelephonyPermissions"; private static final boolean DBG = false; - /** Feature flags */ - private static final FeatureFlags sFeatureFlag = new FeatureFlagsImpl(); + /** * Whether to disable the new device identifier access restrictions. */ @@ -886,12 +883,6 @@ public final class TelephonyPermissions { */ public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId, @NonNull UserHandle callerUserHandle) { - if (!sFeatureFlag.rejectBadSubIdInteraction() - && !SubscriptionManager.isValidSubscriptionId(subId)) { - // Return true for invalid sub Id. - return true; - } - SubscriptionManager subManager = (SubscriptionManager) context.getSystemService( Context.TELEPHONY_SUBSCRIPTION_SERVICE); final long token = Binder.clearCallingIdentity(); @@ -906,7 +897,7 @@ public final class TelephonyPermissions { } catch (IllegalArgumentException e) { // Found no record of this sub Id. Log.e(LOG_TAG, "Subscription[Subscription ID:" + subId + "] has no records on device"); - return !sFeatureFlag.rejectBadSubIdInteraction(); + return false; } finally { Binder.restoreCallingIdentity(token); } diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt index 617237d37368..92b865542257 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt @@ -50,7 +50,7 @@ class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: LegacyFlickerTe testApp.launchViaIntent(wmHelper) testApp.openIME(wmHelper) this.setRotation(flicker.scenario.startRotation) - device.pressRecentApps() + tapl.launchedAppState.switchToOverview() wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify() } transitions { diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java index c1c520e99cac..b98161dd26fb 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java @@ -59,7 +59,8 @@ public class AslConverter { switch (format) { case HUMAN_READABLE: Element appMetadataBundles = - XmlUtils.getSingleElement(document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES); + XmlUtils.getSingleChildElement( + document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES, true); return new AndroidSafetyLabelFactory() .createFromHrElements(XmlUtils.listOf(appMetadataBundles)); diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java index 112b92c9aebb..ecfad91a378f 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java @@ -61,4 +61,10 @@ public class AndroidSafetyLabel implements AslMarshallable { } return XmlUtils.listOf(aslEle); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java index b69c30f7f522..41ce6e55e989 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java @@ -55,4 +55,11 @@ public class AndroidSafetyLabelFactory implements AslMarshallableFactory<Android return new AndroidSafetyLabel( version, systemAppSafetyLabel, safetyLabels, transparencyInfo); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public AndroidSafetyLabel createFromOdElements(List<Element> elements) + throws MalformedXmlException { + return null; + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java index 3f1ddebefe99..21f328d8e2d0 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java @@ -142,4 +142,10 @@ public class AppInfo implements AslMarshallable { } return XmlUtils.listOf(appInfoEle); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java index 59a437d7ece5..6fcf637fe069 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java @@ -72,4 +72,10 @@ public class AppInfoFactory implements AslMarshallableFactory<AppInfo> { email, website); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public AppInfo createFromOdElements(List<Element> elements) throws MalformedXmlException { + return null; + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java index 48747ccbcff6..0a70e7d0d74b 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java @@ -23,6 +23,9 @@ import java.util.List; public interface AslMarshallable { - /** Creates the on-device DOM element from the AslMarshallable Java Object. */ + /** Creates the on-device DOM elements from the AslMarshallable Java Object. */ List<Element> toOdDomElements(Document doc); + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + List<Element> toHrDomElements(Document doc); } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java index a49b3e77155b..39582900f3a0 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java @@ -24,6 +24,9 @@ import java.util.List; public interface AslMarshallableFactory<T extends AslMarshallable> { - /** Creates an {@link AslMarshallableFactory} from human-readable DOM element */ + /** Creates an {@link AslMarshallableFactory} from human-readable DOM elements */ T createFromHrElements(List<Element> elements) throws MalformedXmlException; + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + T createFromOdElements(List<Element> elements) throws MalformedXmlException; } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java index 4d67162b442d..eb975540ce70 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java @@ -59,4 +59,10 @@ public class DataCategory implements AslMarshallable { } return XmlUtils.listOf(dataCategoryEle); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java index 37d99e7ef85e..90424fe00504 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java @@ -50,4 +50,31 @@ public class DataCategoryFactory implements AslMarshallableFactory<DataCategory> return new DataCategory(categoryName, dataTypeMap); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public DataCategory createFromOdElements(List<Element> elements) throws MalformedXmlException { + Element dataCategoryEle = XmlUtils.getSingleElement(elements); + Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>(); + String categoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME); + var odDataTypes = XmlUtils.asElementList(dataCategoryEle.getChildNodes()); + for (Element odDataTypeEle : odDataTypes) { + String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME); + if (!DataTypeConstants.getValidDataTypes().containsKey(categoryName)) { + throw new MalformedXmlException( + String.format("Unrecognized data category %s", categoryName)); + } + if (!DataTypeConstants.getValidDataTypes().get(categoryName).contains(dataTypeName)) { + throw new MalformedXmlException( + String.format( + "Unrecognized data type name %s for category %s", + dataTypeName, categoryName)); + } + dataTypeMap.put( + dataTypeName, + new DataTypeFactory().createFromOdElements(XmlUtils.listOf(odDataTypeEle))); + } + + return new DataCategory(categoryName, dataTypeMap); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java index 7516faf9f77a..4a0d75977d78 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java @@ -79,6 +79,16 @@ public class DataLabels implements AslMarshallable { return XmlUtils.listOf(dataLabelsEle); } + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + Element dataLabelsEle = doc.createElement(XmlUtils.HR_TAG_DATA_LABELS); + maybeAppendHrDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.HR_TAG_DATA_ACCESSED); + maybeAppendHrDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.HR_TAG_DATA_COLLECTED); + maybeAppendHrDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.HR_TAG_DATA_SHARED); + return XmlUtils.listOf(dataLabelsEle); + } + private void maybeAppendDataUsages( Document doc, Element dataLabelsEle, @@ -100,4 +110,42 @@ public class DataLabels implements AslMarshallable { } dataLabelsEle.appendChild(dataUsageEle); } + + private void maybeAppendHrDataUsages( + Document doc, + Element dataLabelsEle, + Map<String, DataCategory> dataCategoriesMap, + String dataUsageTypeName) { + if (dataCategoriesMap.isEmpty()) { + return; + } + for (String dataCategoryName : dataCategoriesMap.keySet()) { + DataCategory dataCategory = dataCategoriesMap.get(dataCategoryName); + for (String dataTypeName : dataCategory.getDataTypes().keySet()) { + DataType dataType = dataCategory.getDataTypes().get(dataTypeName); + // XmlUtils.appendChildren(dataLabelsEle, dataType.toHrDomElements(doc)); + Element hrDataTypeEle = doc.createElement(dataUsageTypeName); + hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY, dataCategoryName); + hrDataTypeEle.setAttribute(XmlUtils.HR_ATTR_DATA_TYPE, dataTypeName); + XmlUtils.maybeSetHrBoolAttr( + hrDataTypeEle, + XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL, + dataType.getIsCollectionOptional()); + XmlUtils.maybeSetHrBoolAttr( + hrDataTypeEle, + XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL, + dataType.getIsSharingOptional()); + XmlUtils.maybeSetHrBoolAttr( + hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, dataType.getEphemeral()); + hrDataTypeEle.setAttribute( + XmlUtils.HR_ATTR_PURPOSES, + String.join( + "|", + dataType.getPurposes().stream() + .map(DataType.Purpose::toString) + .toList())); + dataLabelsEle.appendChild(hrDataTypeEle); + } + } + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java index dc77fd08aa53..5473e010cc65 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java @@ -22,7 +22,6 @@ import com.android.asllib.util.MalformedXmlException; import com.android.asllib.util.XmlUtils; import org.w3c.dom.Element; -import org.w3c.dom.NodeList; import java.util.HashSet; import java.util.LinkedHashMap; @@ -46,9 +45,82 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED); Map<String, DataCategory> dataShared = getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED); + DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared); + validateIsXOptional(dataLabels); + return dataLabels; + } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public DataLabels createFromOdElements(List<Element> elements) throws MalformedXmlException { + Element dataLabelsEle = XmlUtils.getSingleElement(elements); + if (dataLabelsEle == null) { + AslgenUtil.logI("Found no DataLabels in od format."); + return null; + } + Map<String, DataCategory> dataAccessed = + getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_ACCESSED); + Map<String, DataCategory> dataCollected = + getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_COLLECTED); + Map<String, DataCategory> dataShared = + getOdDataCategoriesWithTag(dataLabelsEle, XmlUtils.OD_NAME_DATA_SHARED); + DataLabels dataLabels = new DataLabels(dataAccessed, dataCollected, dataShared); + validateIsXOptional(dataLabels); + return dataLabels; + } + + private static Map<String, DataCategory> getOdDataCategoriesWithTag( + Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException { + Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>(); + Element dataUsageEle = + XmlUtils.getOdPbundleWithName(dataLabelsEle, dataCategoryUsageTypeTag, false); + if (dataUsageEle == null) { + return dataCategoryMap; + } + List<Element> dataCategoryEles = XmlUtils.asElementList(dataUsageEle.getChildNodes()); + for (Element dataCategoryEle : dataCategoryEles) { + String dataCategoryName = dataCategoryEle.getAttribute(XmlUtils.OD_ATTR_NAME); + DataCategory dataCategory = + new DataCategoryFactory().createFromOdElements(List.of(dataCategoryEle)); + dataCategoryMap.put(dataCategoryName, dataCategory); + } + return dataCategoryMap; + } + private static Map<String, DataCategory> getDataCategoriesWithTag( + Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException { + List<Element> dataUsedElements = + XmlUtils.getChildrenByTagName(dataLabelsEle, dataCategoryUsageTypeTag); + Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>(); + + Set<String> dataCategoryNames = new HashSet<String>(); + for (int i = 0; i < dataUsedElements.size(); i++) { + Element dataUsedEle = dataUsedElements.get(i); + String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY); + if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) { + throw new MalformedXmlException( + String.format("Unrecognized category name: %s", dataCategoryName)); + } + dataCategoryNames.add(dataCategoryName); + } + for (String dataCategoryName : dataCategoryNames) { + var dataCategoryElements = + dataUsedElements.stream() + .filter( + ele -> + ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY) + .equals(dataCategoryName)) + .toList(); + DataCategory dataCategory = + new DataCategoryFactory().createFromHrElements(dataCategoryElements); + dataCategoryMap.put(dataCategoryName, dataCategory); + } + return dataCategoryMap; + } + + private void validateIsXOptional(DataLabels dataLabels) throws MalformedXmlException { // Validate booleans such as isCollectionOptional, isSharingOptional. - for (DataCategory dataCategory : dataAccessed.values()) { + for (DataCategory dataCategory : dataLabels.getDataAccessed().values()) { for (DataType dataType : dataCategory.getDataTypes().values()) { if (dataType.getIsSharingOptional() != null) { throw new MalformedXmlException( @@ -66,7 +138,7 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { } } } - for (DataCategory dataCategory : dataCollected.values()) { + for (DataCategory dataCategory : dataLabels.getDataCollected().values()) { for (DataType dataType : dataCategory.getDataTypes().values()) { if (dataType.getIsSharingOptional() != null) { throw new MalformedXmlException( @@ -77,7 +149,7 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { } } } - for (DataCategory dataCategory : dataShared.values()) { + for (DataCategory dataCategory : dataLabels.getDataShared().values()) { for (DataType dataType : dataCategory.getDataTypes().values()) { if (dataType.getIsCollectionOptional() != null) { throw new MalformedXmlException( @@ -88,37 +160,5 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { } } } - - return new DataLabels(dataAccessed, dataCollected, dataShared); - } - - private static Map<String, DataCategory> getDataCategoriesWithTag( - Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException { - NodeList dataUsedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag); - Map<String, DataCategory> dataCategoryMap = new LinkedHashMap<String, DataCategory>(); - - Set<String> dataCategoryNames = new HashSet<String>(); - for (int i = 0; i < dataUsedNodeList.getLength(); i++) { - Element dataUsedEle = (Element) dataUsedNodeList.item(i); - String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY); - if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) { - throw new MalformedXmlException( - String.format("Unrecognized category name: %s", dataCategoryName)); - } - dataCategoryNames.add(dataCategoryName); - } - for (String dataCategoryName : dataCategoryNames) { - var dataCategoryElements = - XmlUtils.asElementList(dataUsedNodeList).stream() - .filter( - ele -> - ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY) - .equals(dataCategoryName)) - .toList(); - DataCategory dataCategory = - new DataCategoryFactory().createFromHrElements(dataCategoryElements); - dataCategoryMap.put(dataCategoryName, dataCategory); - } - return dataCategoryMap; } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java index 347136237966..02b7189c09ba 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java @@ -160,6 +160,12 @@ public class DataType implements AslMarshallable { return XmlUtils.listOf(dataTypeEle); } + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } + private static void maybeAddBoolToOdElement( Document doc, Element parentEle, Boolean b, String odName) { if (b == null) { diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java index ed434cda0823..488c2595912a 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java @@ -51,4 +51,31 @@ public class DataTypeFactory implements AslMarshallableFactory<DataType> { return new DataType( dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public DataType createFromOdElements(List<Element> elements) throws MalformedXmlException { + Element odDataTypeEle = XmlUtils.getSingleElement(elements); + String dataTypeName = odDataTypeEle.getAttribute(XmlUtils.OD_ATTR_NAME); + List<Integer> purposeInts = + XmlUtils.getOdIntArray(odDataTypeEle, XmlUtils.OD_NAME_PURPOSES, true); + List<DataType.Purpose> purposes = + purposeInts.stream().map(DataType.Purpose::forValue).collect(Collectors.toList()); + if (purposes.isEmpty()) { + throw new MalformedXmlException(String.format("Found no purpose in: %s", dataTypeName)); + } + if (new HashSet<>(purposes).size() != purposes.size()) { + throw new MalformedXmlException( + String.format("Found non-unique purposes in: %s", dataTypeName)); + } + Boolean isCollectionOptional = + XmlUtils.getOdBoolEle( + odDataTypeEle, XmlUtils.OD_NAME_IS_COLLECTION_OPTIONAL, false); + Boolean isSharingOptional = + XmlUtils.getOdBoolEle(odDataTypeEle, XmlUtils.OD_NAME_IS_SHARING_OPTIONAL, false); + Boolean ephemeral = XmlUtils.getOdBoolEle(odDataTypeEle, XmlUtils.OD_NAME_EPHEMERAL, false); + + return new DataType( + dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java index 382a1f0d0eca..efdc8d0a5f11 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java @@ -139,4 +139,10 @@ public class DeveloperInfo implements AslMarshallable { return XmlUtils.listOf(developerInfoEle); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java index b5310bac232a..c3e7ac35c545 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java @@ -57,4 +57,10 @@ public class DeveloperInfoFactory implements AslMarshallableFactory<DeveloperInf website, appDeveloperRegistryId); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public DeveloperInfo createFromOdElements(List<Element> elements) throws MalformedXmlException { + return null; + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java index 8c2186a628e3..576820dac6c6 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java @@ -70,4 +70,10 @@ public class SafetyLabels implements AslMarshallable { } return XmlUtils.listOf(safetyLabelsEle); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java index 0f7aa8161c64..7e1838f40680 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java @@ -62,4 +62,10 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> false))); return new SafetyLabels(version, dataLabels, securityLabels, thirdPartyVerification); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public SafetyLabels createFromOdElements(List<Element> elements) throws MalformedXmlException { + return null; + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java index 529b50364255..437343b14605 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabels.java @@ -50,4 +50,10 @@ public class SecurityLabels implements AslMarshallable { } return XmlUtils.listOf(ele); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java index 84024520035e..9dc4712c33b0 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SecurityLabelsFactory.java @@ -41,4 +41,11 @@ public class SecurityLabelsFactory implements AslMarshallableFactory<SecurityLab XmlUtils.getBoolAttr(ele, XmlUtils.HR_ATTR_IS_DATA_ENCRYPTED, false); return new SecurityLabels(isDataDeletable, isDataEncrypted); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public SecurityLabels createFromOdElements(List<Element> elements) + throws MalformedXmlException { + return null; + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java index 595d748b59af..f0ecf93f2805 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java @@ -46,4 +46,10 @@ public class SystemAppSafetyLabel implements AslMarshallable { XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl)); return XmlUtils.listOf(systemAppSafetyLabelEle); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java index f99955993d6c..5b7fe32f2735 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java @@ -39,4 +39,11 @@ public class SystemAppSafetyLabelFactory implements AslMarshallableFactory<Syste String url = XmlUtils.getStringAttr(systemAppSafetyLabelEle, XmlUtils.HR_ATTR_URL); return new SystemAppSafetyLabel(url); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public SystemAppSafetyLabel createFromOdElements(List<Element> elements) + throws MalformedXmlException { + return null; + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java index a1b22f885fe6..229b00243e0a 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerification.java @@ -40,4 +40,10 @@ public class ThirdPartyVerification implements AslMarshallable { ele.appendChild(XmlUtils.createOdStringEle(doc, XmlUtils.OD_NAME_URL, mUrl)); return XmlUtils.listOf(ele); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java index c3e4964c0b9d..ac4d3836bcbd 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/ThirdPartyVerificationFactory.java @@ -40,4 +40,11 @@ public class ThirdPartyVerificationFactory String url = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_URL); return new ThirdPartyVerification(url); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public ThirdPartyVerification createFromOdElements(List<Element> elements) + throws MalformedXmlException { + return null; + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java index ddd3557616ca..ce7ef16ea54e 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java @@ -57,4 +57,10 @@ public class TransparencyInfo implements AslMarshallable { } return XmlUtils.listOf(transparencyInfoEle); } + + /** Creates the human-readable DOM elements from the AslMarshallable Java Object. */ + @Override + public List<Element> toHrDomElements(Document doc) { + return List.of(); + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java index d9c2af41fcac..123de01e57ba 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java @@ -49,4 +49,11 @@ public class TransparencyInfoFactory implements AslMarshallableFactory<Transpare return new TransparencyInfo(developerInfo, appInfo); } + + /** Creates an {@link AslMarshallableFactory} from on-device DOM elements */ + @Override + public TransparencyInfo createFromOdElements(List<Element> elements) + throws MalformedXmlException { + return null; + } } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java index ed3d7f8372d6..4f21b0c0ffad 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java @@ -16,8 +16,10 @@ package com.android.asllib.util; + import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.ArrayList; @@ -118,59 +120,37 @@ public class XmlUtils { public static final String TRUE_STR = "true"; public static final String FALSE_STR = "false"; - /** Gets the single top-level {@link Element} having the {@param tagName}. */ - public static Element getSingleElement(Document doc, String tagName) - throws MalformedXmlException { - var elements = doc.getElementsByTagName(tagName); - return getSingleElement(elements, tagName); + /** Gets the top-level children with the tag name.. */ + public static List<Element> getChildrenByTagName(Node parentEle, String tagName) { + var elements = XmlUtils.asElementList(parentEle.getChildNodes()); + return elements.stream().filter(e -> e.getTagName().equals(tagName)).toList(); } /** * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}. */ - public static Element getSingleChildElement(Element parentEle, String tagName) + public static Element getSingleChildElement(Node parentEle, String tagName, boolean required) throws MalformedXmlException { - var elements = parentEle.getElementsByTagName(tagName); - return getSingleElement(elements, tagName, true); - } + String parentTagNameForErrorMsg = + (parentEle instanceof Element) ? ((Element) parentEle).getTagName() : "Node"; + var elements = getChildrenByTagName(parentEle, tagName); - /** - * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}. - */ - public static Element getSingleChildElement(Element parentEle, String tagName, boolean required) - throws MalformedXmlException { - var elements = parentEle.getElementsByTagName(tagName); - return getSingleElement(elements, tagName, required); - } - - /** Gets the single {@link Element} from {@param elements} */ - public static Element getSingleElement(NodeList elements, String tagName) - throws MalformedXmlException { - return getSingleElement(elements, tagName, true); - } - - /** Gets the single {@link Element} from {@param elements} */ - public static Element getSingleElement(NodeList elements, String tagName, boolean required) - throws MalformedXmlException { - if (elements.getLength() > 1) { + if (elements.size() > 1) { throw new MalformedXmlException( String.format( - "Expected 1 element \"%s\" in NodeList but got %s.", - tagName, elements.getLength())); - } else if (elements.getLength() == 0) { + "Expected 1 %s in %s but got %s.", + tagName, parentTagNameForErrorMsg, elements.size())); + } else if (elements.isEmpty()) { if (required) { throw new MalformedXmlException( - String.format("Found no element \"%s\" in NodeList.", tagName)); + String.format( + "Expected 1 %s in %s but got 0.", + tagName, parentTagNameForErrorMsg)); } else { return null; } } - var elementAsNode = elements.item(0); - if (!(elementAsNode instanceof Element)) { - throw new MalformedXmlException( - String.format("%s was not a valid XML element.", tagName)); - } - return ((Element) elementAsNode); + return elements.get(0); } /** Gets the single {@link Element} within {@param elements}. */ @@ -229,6 +209,13 @@ public class XmlUtils { return ele; } + /** Sets human-readable bool attribute if non-null. */ + public static void maybeSetHrBoolAttr(Element ele, String attrName, Boolean b) { + if (b != null) { + ele.setAttribute(attrName, String.valueOf(b)); + } + } + /** Create an on-device Long DOM Element with the given attribute name. */ public static Element createOdLongEle(Document doc, String name, long l) { var ele = doc.createElement(XmlUtils.OD_TAG_LONG); @@ -303,6 +290,57 @@ public class XmlUtils { return b; } + /** Gets a Boolean attribute. */ + public static Boolean getOdBoolEle(Element ele, String nameName, boolean required) + throws MalformedXmlException { + List<Element> boolEles = + XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_BOOLEAN).stream() + .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) + .toList(); + if (boolEles.size() > 1) { + throw new MalformedXmlException( + String.format("Found more than one %s in %s.", nameName, ele.getTagName())); + } + if (boolEles.isEmpty()) { + if (required) { + throw new MalformedXmlException( + String.format("Found no %s in %s.", nameName, ele.getTagName())); + } + return null; + } + Element boolEle = boolEles.get(0); + + Boolean b = XmlUtils.fromString(boolEle.getAttribute(XmlUtils.OD_ATTR_VALUE)); + if (b == null && required) { + throw new MalformedXmlException( + String.format( + "Boolean %s was required but missing, in %s.", + nameName, ele.getTagName())); + } + return b; + } + + /** Gets a OD Pbundle Element attribute with the specified name. */ + public static Element getOdPbundleWithName(Element ele, String nameName, boolean required) + throws MalformedXmlException { + List<Element> eles = + XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_PBUNDLE_AS_MAP).stream() + .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) + .toList(); + if (eles.size() > 1) { + throw new MalformedXmlException( + String.format("Found more than one %s in %s.", nameName, ele.getTagName())); + } + if (eles.isEmpty()) { + if (required) { + throw new MalformedXmlException( + String.format("Found no %s in %s.", nameName, ele.getTagName())); + } + return null; + } + return eles.get(0); + } + /** Gets a required String attribute. */ public static String getStringAttr(Element ele, String attrName) throws MalformedXmlException { return getStringAttr(ele, attrName, true); @@ -325,6 +363,33 @@ public class XmlUtils { return s; } + /** Gets on-device style int array. */ + public static List<Integer> getOdIntArray(Element ele, String nameName, boolean required) + throws MalformedXmlException { + List<Element> intArrayEles = + XmlUtils.getChildrenByTagName(ele, XmlUtils.OD_TAG_INT_ARRAY).stream() + .filter(e -> e.getAttribute(XmlUtils.OD_ATTR_NAME).equals(nameName)) + .toList(); + if (intArrayEles.size() > 1) { + throw new MalformedXmlException( + String.format("Found more than one %s in %s.", nameName, ele.getTagName())); + } + if (intArrayEles.isEmpty()) { + if (required) { + throw new MalformedXmlException( + String.format("Found no %s in %s.", nameName, ele.getTagName())); + } + return List.of(); + } + Element intArrayEle = intArrayEles.get(0); + List<Element> itemEles = XmlUtils.getChildrenByTagName(intArrayEle, XmlUtils.OD_TAG_ITEM); + List<Integer> ints = new ArrayList<Integer>(); + for (Element itemEle : itemEles) { + ints.add(Integer.parseInt(XmlUtils.getStringAttr(itemEle, XmlUtils.OD_ATTR_VALUE))); + } + return ints; + } + /** * Utility method for making a List from one element, to support easier refactoring if needed. * For example, List.of() doesn't support null elements. diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java index 2be447e182b2..6f6f2545a5d2 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java @@ -41,6 +41,30 @@ public class DataLabelsTest { private static final String SHARED_INVALID_BOOL_FILE_NAME = "data-labels-shared-invalid-bool.xml"; + private static final String ACTIONS_IN_APP_FILE_NAME = "data-category-actions-in-app.xml"; + private static final String APP_PERFORMANCE_FILE_NAME = "data-category-app-performance.xml"; + private static final String AUDIO_FILE_NAME = "data-category-audio.xml"; + private static final String CALENDAR_FILE_NAME = "data-category-calendar.xml"; + private static final String CONTACTS_FILE_NAME = "data-category-contacts.xml"; + private static final String EMAIL_TEXT_MESSAGE_FILE_NAME = + "data-category-email-text-message.xml"; + private static final String FINANCIAL_FILE_NAME = "data-category-financial.xml"; + private static final String HEALTH_FITNESS_FILE_NAME = "data-category-health-fitness.xml"; + private static final String IDENTIFIERS_FILE_NAME = "data-category-identifiers.xml"; + private static final String LOCATION_FILE_NAME = "data-category-location.xml"; + private static final String PERSONAL_FILE_NAME = "data-category-personal.xml"; + private static final String PERSONAL_PARTIAL_FILE_NAME = "data-category-personal-partial.xml"; + private static final String PHOTO_VIDEO_FILE_NAME = "data-category-photo-video.xml"; + private static final String SEARCH_AND_BROWSING_FILE_NAME = + "data-category-search-and-browsing.xml"; + private static final String STORAGE_FILE_NAME = "data-category-storage.xml"; + private static final String PERSONAL_MISSING_PURPOSE_FILE_NAME = + "data-category-personal-missing-purpose.xml"; + private static final String PERSONAL_EMPTY_PURPOSE_FILE_NAME = + "data-category-personal-empty-purpose.xml"; + private static final String UNRECOGNIZED_FILE_NAME = "data-category-unrecognized.xml"; + private static final String UNRECOGNIZED_TYPE_FILE_NAME = "data-category-unrecognized-type.xml"; + private Document mDoc = null; @Before @@ -54,6 +78,7 @@ public class DataLabelsTest { public void testDataLabelsAccessedValidBool() throws Exception { System.out.println("starting testDataLabelsAccessedValidBool."); testHrToOdDataLabels(ACCESSED_VALID_BOOL_FILE_NAME); + testOdToHrDataLabels(ACCESSED_VALID_BOOL_FILE_NAME); } /** Test for data labels accessed invalid bool. */ @@ -68,6 +93,7 @@ public class DataLabelsTest { public void testDataLabelsCollectedValidBool() throws Exception { System.out.println("starting testDataLabelsCollectedValidBool."); testHrToOdDataLabels(COLLECTED_VALID_BOOL_FILE_NAME); + testOdToHrDataLabels(COLLECTED_VALID_BOOL_FILE_NAME); } /** Test for data labels collected invalid bool. */ @@ -75,6 +101,7 @@ public class DataLabelsTest { public void testDataLabelsCollectedInvalidBool() throws Exception { System.out.println("starting testDataLabelsCollectedInvalidBool."); hrToOdExpectException(COLLECTED_INVALID_BOOL_FILE_NAME); + odToHrExpectException(COLLECTED_INVALID_BOOL_FILE_NAME); } /** Test for data labels shared valid bool. */ @@ -82,6 +109,7 @@ public class DataLabelsTest { public void testDataLabelsSharedValidBool() throws Exception { System.out.println("starting testDataLabelsSharedValidBool."); testHrToOdDataLabels(SHARED_VALID_BOOL_FILE_NAME); + testOdToHrDataLabels(SHARED_VALID_BOOL_FILE_NAME); } /** Test for data labels shared invalid bool. */ @@ -91,12 +119,207 @@ public class DataLabelsTest { hrToOdExpectException(SHARED_INVALID_BOOL_FILE_NAME); } + /* Data categories bidirectional tests... */ + + /** Test for data labels actions in app. */ + @Test + public void testDataLabelsActionsInApp() throws Exception { + System.out.println("starting testDataLabelsActionsInApp."); + testHrToOdDataLabels(ACTIONS_IN_APP_FILE_NAME); + testOdToHrDataLabels(ACTIONS_IN_APP_FILE_NAME); + } + + /** Test for data labels app performance. */ + @Test + public void testDataLabelsAppPerformance() throws Exception { + System.out.println("starting testDataLabelsAppPerformance."); + testHrToOdDataLabels(APP_PERFORMANCE_FILE_NAME); + testOdToHrDataLabels(APP_PERFORMANCE_FILE_NAME); + } + + /** Test for data labels audio. */ + @Test + public void testDataLabelsAudio() throws Exception { + System.out.println("starting testDataLabelsAudio."); + testHrToOdDataLabels(AUDIO_FILE_NAME); + testOdToHrDataLabels(AUDIO_FILE_NAME); + } + + /** Test for data labels calendar. */ + @Test + public void testDataLabelsCalendar() throws Exception { + System.out.println("starting testDataLabelsCalendar."); + testHrToOdDataLabels(CALENDAR_FILE_NAME); + testOdToHrDataLabels(CALENDAR_FILE_NAME); + } + + /** Test for data labels contacts. */ + @Test + public void testDataLabelsContacts() throws Exception { + System.out.println("starting testDataLabelsContacts."); + testHrToOdDataLabels(CONTACTS_FILE_NAME); + testOdToHrDataLabels(CONTACTS_FILE_NAME); + } + + /** Test for data labels email text message. */ + @Test + public void testDataLabelsEmailTextMessage() throws Exception { + System.out.println("starting testDataLabelsEmailTextMessage."); + testHrToOdDataLabels(EMAIL_TEXT_MESSAGE_FILE_NAME); + testOdToHrDataLabels(EMAIL_TEXT_MESSAGE_FILE_NAME); + } + + /** Test for data labels financial. */ + @Test + public void testDataLabelsFinancial() throws Exception { + System.out.println("starting testDataLabelsFinancial."); + testHrToOdDataLabels(FINANCIAL_FILE_NAME); + testOdToHrDataLabels(FINANCIAL_FILE_NAME); + } + + /** Test for data labels health fitness. */ + @Test + public void testDataLabelsHealthFitness() throws Exception { + System.out.println("starting testDataLabelsHealthFitness."); + testHrToOdDataLabels(HEALTH_FITNESS_FILE_NAME); + testOdToHrDataLabels(HEALTH_FITNESS_FILE_NAME); + } + + /** Test for data labels identifiers. */ + @Test + public void testDataLabelsIdentifiers() throws Exception { + System.out.println("starting testDataLabelsIdentifiers."); + testHrToOdDataLabels(IDENTIFIERS_FILE_NAME); + testOdToHrDataLabels(IDENTIFIERS_FILE_NAME); + } + + /** Test for data labels location. */ + @Test + public void testDataLabelsLocation() throws Exception { + System.out.println("starting testDataLabelsLocation."); + testHrToOdDataLabels(LOCATION_FILE_NAME); + testOdToHrDataLabels(LOCATION_FILE_NAME); + } + + /** Test for data labels personal. */ + @Test + public void testDataLabelsPersonal() throws Exception { + System.out.println("starting testDataLabelsPersonal."); + testHrToOdDataLabels(PERSONAL_FILE_NAME); + testOdToHrDataLabels(PERSONAL_FILE_NAME); + } + + /** Test for data labels personal partial. */ + @Test + public void testDataLabelsPersonalPartial() throws Exception { + System.out.println("starting testDataLabelsPersonalPartial."); + testHrToOdDataLabels(PERSONAL_PARTIAL_FILE_NAME); + testOdToHrDataLabels(PERSONAL_PARTIAL_FILE_NAME); + } + + /** Test for data labels photo video. */ + @Test + public void testDataLabelsPhotoVideo() throws Exception { + System.out.println("starting testDataLabelsPhotoVideo."); + testHrToOdDataLabels(PHOTO_VIDEO_FILE_NAME); + testOdToHrDataLabels(PHOTO_VIDEO_FILE_NAME); + } + + /** Test for data labels search and browsing. */ + @Test + public void testDataLabelsSearchAndBrowsing() throws Exception { + System.out.println("starting testDataLabelsSearchAndBrowsing."); + testHrToOdDataLabels(SEARCH_AND_BROWSING_FILE_NAME); + testOdToHrDataLabels(SEARCH_AND_BROWSING_FILE_NAME); + } + + /** Test for data labels storage. */ + @Test + public void testDataLabelsStorage() throws Exception { + System.out.println("starting testDataLabelsStorage."); + testHrToOdDataLabels(STORAGE_FILE_NAME); + testOdToHrDataLabels(STORAGE_FILE_NAME); + } + + /** Test for data labels hr unrecognized data category. */ + @Test + public void testDataLabelsHrUnrecognizedDataCategory() throws Exception { + System.out.println("starting testDataLabelsHrUnrecognizedDataCategory."); + hrToOdExpectException(UNRECOGNIZED_FILE_NAME); + } + + /** Test for data labels hr unrecognized data type. */ + @Test + public void testDataLabelsHrUnrecognizedDataType() throws Exception { + System.out.println("starting testDataLabelsHrUnrecognizedDataType."); + hrToOdExpectException(UNRECOGNIZED_TYPE_FILE_NAME); + } + + /** Test for data labels hr missing purpose. */ + @Test + public void testDataLabelsHrMissingPurpose() throws Exception { + System.out.println("starting testDataLabelsHrMissingPurpose."); + hrToOdExpectException(PERSONAL_MISSING_PURPOSE_FILE_NAME); + } + + /** Test for data labels hr empty purpose. */ + @Test + public void testDataLabelsHrEmptyPurpose() throws Exception { + System.out.println("starting testDataLabelsHrEmptyPurpose."); + hrToOdExpectException(PERSONAL_EMPTY_PURPOSE_FILE_NAME); + } + + /** Test for data labels od unrecognized data category. */ + @Test + public void testDataLabelsOdUnrecognizedDataCategory() throws Exception { + System.out.println("starting testDataLabelsOdUnrecognizedDataCategory."); + odToHrExpectException(UNRECOGNIZED_FILE_NAME); + } + + /** Test for data labels od unrecognized data type. */ + @Test + public void testDataLabelsOdUnrecognizedDataType() throws Exception { + System.out.println("starting testDataLabelsOdUnrecognizedDataCategory."); + odToHrExpectException(UNRECOGNIZED_TYPE_FILE_NAME); + } + + /** Test for data labels od missing purpose. */ + @Test + public void testDataLabelsOdMissingPurpose() throws Exception { + System.out.println("starting testDataLabelsOdMissingPurpose."); + odToHrExpectException(PERSONAL_MISSING_PURPOSE_FILE_NAME); + } + + /** Test for data labels od empty purpose. */ + @Test + public void testDataLabelsOdEmptyPurpose() throws Exception { + System.out.println("starting testDataLabelsOdEmptyPurpose."); + odToHrExpectException(PERSONAL_EMPTY_PURPOSE_FILE_NAME); + } + private void hrToOdExpectException(String fileName) { TestUtils.hrToOdExpectException(new DataLabelsFactory(), DATA_LABELS_HR_PATH, fileName); } + private void odToHrExpectException(String fileName) { + TestUtils.odToHrExpectException(new DataLabelsFactory(), DATA_LABELS_OD_PATH, fileName); + } + private void testHrToOdDataLabels(String fileName) throws Exception { TestUtils.testHrToOd( - mDoc, new DataLabelsFactory(), DATA_LABELS_HR_PATH, DATA_LABELS_OD_PATH, fileName); + TestUtils.document(), + new DataLabelsFactory(), + DATA_LABELS_HR_PATH, + DATA_LABELS_OD_PATH, + fileName); + } + + private void testOdToHrDataLabels(String fileName) throws Exception { + TestUtils.testOdToHr( + TestUtils.document(), + new DataLabelsFactory(), + DATA_LABELS_OD_PATH, + DATA_LABELS_HR_PATH, + fileName); } } diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java index faea340ae7bd..6a29b869be43 100644 --- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java +++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java @@ -26,6 +26,8 @@ import com.android.asllib.util.XmlUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import java.io.ByteArrayInputStream; @@ -72,7 +74,7 @@ public class TestUtils { .findFirst() .get() .getTagName(); - return XmlUtils.asElementList(root.getElementsByTagName(tagName)); + return XmlUtils.getChildrenByTagName(root, tagName); } else { return List.of(root); } @@ -105,7 +107,7 @@ public class TestUtils { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); Document document = factory.newDocumentBuilder().parse(stream); - + stripEmptyElements(document); return docToStr(document, omitXmlDeclaration); } @@ -125,6 +127,17 @@ public class TestUtils { }); } + /** Helper for testing on-device to human-readable conversion expecting exception */ + public static <T extends AslMarshallable> void odToHrExpectException( + AslMarshallableFactory<T> factory, String odFolderPath, String fileName) { + assertThrows( + MalformedXmlException.class, + () -> { + factory.createFromOdElements( + TestUtils.getElementsFromResource(Paths.get(odFolderPath, fileName))); + }); + } + /** Helper for testing human-readable to on-device conversion */ public static <T extends AslMarshallable> void testHrToOd( Document doc, @@ -133,20 +146,71 @@ public class TestUtils { String odFolderPath, String fileName) throws Exception { + testFormatToFormat(doc, factory, hrFolderPath, odFolderPath, fileName, true); + } + + /** Helper for testing on-device to human-readable conversion */ + public static <T extends AslMarshallable> void testOdToHr( + Document doc, + AslMarshallableFactory<T> factory, + String odFolderPath, + String hrFolderPath, + String fileName) + throws Exception { + testFormatToFormat(doc, factory, odFolderPath, hrFolderPath, fileName, false); + } + + /** Helper for testing format to format conversion */ + private static <T extends AslMarshallable> void testFormatToFormat( + Document doc, + AslMarshallableFactory<T> factory, + String inFolderPath, + String outFolderPath, + String fileName, + boolean hrToOd) + throws Exception { AslMarshallable marshallable = - factory.createFromHrElements( - TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName))); + hrToOd + ? factory.createFromHrElements( + TestUtils.getElementsFromResource( + Paths.get(inFolderPath, fileName))) + : factory.createFromOdElements( + TestUtils.getElementsFromResource( + Paths.get(inFolderPath, fileName))); + + List<Element> elements = + hrToOd ? marshallable.toOdDomElements(doc) : marshallable.toHrDomElements(doc); + if (elements.isEmpty()) { + throw new IllegalStateException("elements was empty."); + } else if (elements.size() == 1) { + doc.appendChild(elements.get(0)); + } else { + Element root = doc.createElement(TestUtils.HOLDER_TAG_NAME); + for (var child : elements) { + root.appendChild(child); + } + doc.appendChild(root); + } + String converted = TestUtils.getFormattedXml(TestUtils.docToStr(doc, true), true); + System.out.println("Converted: " + converted); + String expectedOutContents = + TestUtils.getFormattedXml( + TestUtils.readStrFromResource(Paths.get(outFolderPath, fileName)), true); + System.out.println("Expected: " + expectedOutContents); + assertEquals(expectedOutContents, converted); + } - for (var child : marshallable.toOdDomElements(doc)) { - doc.appendChild(child); + private static void stripEmptyElements(Node node) { + NodeList children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); ++i) { + Node child = children.item(i); + if (child.getNodeType() == Node.TEXT_NODE) { + if (child.getTextContent().trim().length() == 0) { + child.getParentNode().removeChild(child); + i--; + } + } + stripEmptyElements(child); } - String converted = TestUtils.docToStr(doc, true); - System.out.println("converted: " + converted); - - String expectedOdContents = - TestUtils.readStrFromResource(Paths.get(odFolderPath, fileName)); - assertEquals( - TestUtils.getFormattedXml(expectedOdContents, true), - TestUtils.getFormattedXml(converted, true)); } } diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml new file mode 100644 index 000000000000..68e191e8ebe3 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-actions-in-app.xml @@ -0,0 +1,17 @@ +<data-labels> + <data-shared dataCategory="actions_in_app" + dataType="user_interaction" + purposes="analytics" /> + <data-shared dataCategory="actions_in_app" + dataType="in_app_search_history" + purposes="analytics" /> + <data-shared dataCategory="actions_in_app" + dataType="installed_apps" + purposes="analytics" /> + <data-shared dataCategory="actions_in_app" + dataType="user_generated_content" + purposes="analytics" /> + <data-shared dataCategory="actions_in_app" + dataType="other" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml new file mode 100644 index 000000000000..a6bd17d63704 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-app-performance.xml @@ -0,0 +1,11 @@ +<data-labels> + <data-shared dataCategory="app_performance" + dataType="crash_logs" + purposes="analytics" /> + <data-shared dataCategory="app_performance" + dataType="performance_diagnostics" + purposes="analytics" /> + <data-shared dataCategory="app_performance" + dataType="other" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml new file mode 100644 index 000000000000..6274604f3431 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-audio.xml @@ -0,0 +1,11 @@ +<data-labels> + <data-shared dataCategory="audio" + dataType="sound_recordings" + purposes="analytics" /> + <data-shared dataCategory="audio" + dataType="music_files" + purposes="analytics" /> + <data-shared dataCategory="audio" + dataType="other" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml new file mode 100644 index 000000000000..f7201f625629 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-calendar.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="calendar" + dataType="calendar" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml new file mode 100644 index 000000000000..e8d40be91d90 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-contacts.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="contacts" + dataType="contacts" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml new file mode 100644 index 000000000000..69e9b87db287 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-email-text-message.xml @@ -0,0 +1,11 @@ +<data-labels> + <data-shared dataCategory="email_text_message" + dataType="emails" + purposes="analytics" /> + <data-shared dataCategory="email_text_message" + dataType="text_messages" + purposes="analytics" /> + <data-shared dataCategory="email_text_message" + dataType="other" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml new file mode 100644 index 000000000000..fdd84569a5df --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-financial.xml @@ -0,0 +1,14 @@ +<data-labels> + <data-shared dataCategory="financial" + dataType="card_bank_account" + purposes="analytics" /> + <data-shared dataCategory="financial" + dataType="purchase_history" + purposes="analytics" /> + <data-shared dataCategory="financial" + dataType="credit_score" + purposes="analytics" /> + <data-shared dataCategory="financial" + dataType="other" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml new file mode 100644 index 000000000000..bac58e6fb7df --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-health-fitness.xml @@ -0,0 +1,8 @@ +<data-labels> + <data-shared dataCategory="health_fitness" + dataType="health" + purposes="analytics" /> + <data-shared dataCategory="health_fitness" + dataType="fitness" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml new file mode 100644 index 000000000000..ee45f269eb7f --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-identifiers.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="identifiers" + dataType="other" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml new file mode 100644 index 000000000000..e8e59118f69c --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-location.xml @@ -0,0 +1,8 @@ +<data-labels> + <data-shared dataCategory="location" + dataType="approx_location" + purposes="analytics" /> + <data-shared dataCategory="location" + dataType="precise_location" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml new file mode 100644 index 000000000000..0b220f43e5af --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-empty-purpose.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="personal" + dataType="email_address" + purposes="" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml new file mode 100644 index 000000000000..ac221f208577 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-missing-purpose.xml @@ -0,0 +1,4 @@ +<data-labels> + <data-shared dataCategory="personal" + dataType="email_address" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml new file mode 100644 index 000000000000..11b73689230b --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-partial.xml @@ -0,0 +1,8 @@ +<data-labels> + <data-shared dataCategory="personal" + dataType="name" + purposes="analytics|developer_communications" /> + <data-shared dataCategory="personal" + dataType="email_address" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml new file mode 100644 index 000000000000..f1fbd56571b2 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal-unrecognized-type.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="personal" + dataType="unrecognized" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml new file mode 100644 index 000000000000..59074628de70 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-personal.xml @@ -0,0 +1,31 @@ +<data-labels> + <data-shared dataCategory="personal" + dataType="name" + ephemeral="true" + isSharingOptional="true" + purposes="analytics|developer_communications" /> + <data-shared dataCategory="personal" + dataType="email_address" + purposes="analytics" /> + <data-shared dataCategory="personal" + dataType="physical_address" + purposes="analytics" /> + <data-shared dataCategory="personal" + dataType="phone_number" + purposes="analytics" /> + <data-shared dataCategory="personal" + dataType="race_ethnicity" + purposes="analytics" /> + <data-shared dataCategory="personal" + dataType="political_or_religious_beliefs" + purposes="analytics" /> + <data-shared dataCategory="personal" + dataType="sexual_orientation_or_gender_identity" + purposes="analytics" /> + <data-shared dataCategory="personal" + dataType="personal_identifiers" + purposes="analytics" /> + <data-shared dataCategory="personal" + dataType="other" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml new file mode 100644 index 000000000000..05fe159a1d7c --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-photo-video.xml @@ -0,0 +1,8 @@ +<data-labels> + <data-shared dataCategory="photo_video" + dataType="photos" + purposes="analytics" /> + <data-shared dataCategory="photo_video" + dataType="videos" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml new file mode 100644 index 000000000000..a5de7bed4152 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-search-and-browsing.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="search_and_browsing" + dataType="web_browsing_history" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml new file mode 100644 index 000000000000..f01e2df8d99a --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-storage.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="storage" + dataType="files_docs" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml new file mode 100644 index 000000000000..f1fbd56571b2 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized-type.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="personal" + dataType="unrecognized" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml new file mode 100644 index 000000000000..c5be68424226 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-category-unrecognized.xml @@ -0,0 +1,5 @@ +<data-labels> + <data-shared dataCategory="unrecognized" + dataType="email_address" + purposes="analytics" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml new file mode 100644 index 000000000000..161057a51b79 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-collected-shared.xml @@ -0,0 +1,11 @@ +<data-labels> + <data-accessed dataCategory="location" + dataType="approx_location" + purposes="app_functionality" /> + <data-collected dataCategory="location" + dataType="precise_location" + purposes="app_functionality" /> + <data-shared dataCategory="personal" + dataType="name" + purposes="app_functionality" /> +</data-labels>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml new file mode 100644 index 000000000000..c5fef58cb25c --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-actions-in-app.xml @@ -0,0 +1,31 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="actions_in_app"> + <pbundle_as_map name="user_interaction"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="in_app_search_history"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="installed_apps"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="user_generated_content"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="other"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + </pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml new file mode 100644 index 000000000000..4570145a0436 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-app-performance.xml @@ -0,0 +1,21 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="app_performance"> + <pbundle_as_map name="crash_logs"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="performance_diagnostics"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="other"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml new file mode 100644 index 000000000000..120f626549f7 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-audio.xml @@ -0,0 +1,21 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="audio"> + <pbundle_as_map name="sound_recordings"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="music_files"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="other"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml new file mode 100644 index 000000000000..59eb93812690 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-calendar.xml @@ -0,0 +1,11 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="calendar"> + <pbundle_as_map name="calendar"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml new file mode 100644 index 000000000000..f952bfb4df79 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-contacts.xml @@ -0,0 +1,11 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="contacts"> + <pbundle_as_map name="contacts"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml new file mode 100644 index 000000000000..bcaa716559d4 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-email-text-message.xml @@ -0,0 +1,21 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="email_text_message"> + <pbundle_as_map name="emails"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="text_messages"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="other"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml new file mode 100644 index 000000000000..a7bc82e38dac --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-financial.xml @@ -0,0 +1,26 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="financial"> + <pbundle_as_map name="card_bank_account"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="purchase_history"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="credit_score"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="other"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml new file mode 100644 index 000000000000..f3ab2bd90733 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-health-fitness.xml @@ -0,0 +1,16 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="health_fitness"> + <pbundle_as_map name="health"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="fitness"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml new file mode 100644 index 000000000000..05c07e54c5a8 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-identifiers.xml @@ -0,0 +1,11 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="identifiers"> + <pbundle_as_map name="other"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml new file mode 100644 index 000000000000..931d1adc0218 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-location.xml @@ -0,0 +1,16 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="location"> + <pbundle_as_map name="approx_location"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="precise_location"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml new file mode 100644 index 000000000000..83f4a67cd54d --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-empty-purpose.xml @@ -0,0 +1,11 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="personal"> + <pbundle_as_map name="email_address"> + + <int-array name="purposes" num="2"> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml new file mode 100644 index 000000000000..532f5deee655 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-missing-purpose.xml @@ -0,0 +1,8 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="personal"> + <pbundle_as_map name="email_address"> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml new file mode 100644 index 000000000000..14f9ef2b74ad --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal-partial.xml @@ -0,0 +1,17 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="personal"> + <pbundle_as_map name="name"> + <int-array name="purposes" num="2"> + <item value="2" /> + <item value="3" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="email_address"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml new file mode 100644 index 000000000000..1c87de9e67a7 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-personal.xml @@ -0,0 +1,54 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="personal"> + <pbundle_as_map name="name"> + <int-array name="purposes" num="2"> + <item value="2" /> + <item value="3" /> + </int-array> + <boolean name="is_sharing_optional" value="true" /> + <boolean name="ephemeral" value="true" /> + </pbundle_as_map> + <pbundle_as_map name="email_address"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="physical_address"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="phone_number"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="race_ethnicity"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="political_or_religious_beliefs"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="sexual_orientation_or_gender_identity"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="personal_identifiers"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="other"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml new file mode 100644 index 000000000000..a752b5152c25 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-photo-video.xml @@ -0,0 +1,16 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="photo_video"> + <pbundle_as_map name="photos"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> + <pbundle_as_map name="videos"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml new file mode 100644 index 000000000000..99bc1881b4f1 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-search-and-browsing.xml @@ -0,0 +1,11 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="search_and_browsing"> + <pbundle_as_map name="web_browsing_history"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml new file mode 100644 index 000000000000..a4d2a6249119 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-storage.xml @@ -0,0 +1,11 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="storage"> + <pbundle_as_map name="files_docs"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml new file mode 100644 index 000000000000..16195df53b3a --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized-type.xml @@ -0,0 +1,11 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="personal"> + <pbundle_as_map name="unrecognized"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml new file mode 100644 index 000000000000..511940eafd4a --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-category-unrecognized.xml @@ -0,0 +1,11 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="unrecognized"> + <pbundle_as_map name="email_address"> + <int-array name="purposes" num="1"> + <item value="2" /> + </int-array> + </pbundle_as_map> +</pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml new file mode 100644 index 000000000000..f86dbc1a63b2 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-collected-shared.xml @@ -0,0 +1,29 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_accessed"> + <pbundle_as_map name="location"> + <pbundle_as_map name="approx_location"> + <int-array name="purposes" num="1"> + <item value="1"/> + </int-array> + </pbundle_as_map> + </pbundle_as_map> + </pbundle_as_map> + <pbundle_as_map name="data_collected"> + <pbundle_as_map name="location"> + <pbundle_as_map name="precise_location"> + <int-array name="purposes" num="1"> + <item value="1"/> + </int-array> + </pbundle_as_map> + </pbundle_as_map> + </pbundle_as_map> + <pbundle_as_map name="data_shared"> + <pbundle_as_map name="personal"> + <pbundle_as_map name="name"> + <int-array name="purposes" num="1"> + <item value="1"/> + </int-array> + </pbundle_as_map> + </pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml new file mode 100644 index 000000000000..54cc8e7049b2 --- /dev/null +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-invalid-bool.xml @@ -0,0 +1,13 @@ +<pbundle_as_map name="data_labels"> + <pbundle_as_map name="data_collected"> + <pbundle_as_map name="location"> + <pbundle_as_map name="approx_location"> + <int-array name="purposes" num="1"> + <item value="1"/> + </int-array> + <boolean name="is_sharing_optional" value="false"/> + <boolean name="ephemeral" value="false"/> + </pbundle_as_map> + </pbundle_as_map> + </pbundle_as_map> +</pbundle_as_map>
\ No newline at end of file diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml index d1d4e33e855a..3864f986d20d 100644 --- a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml +++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml @@ -1,5 +1,5 @@ <pbundle_as_map name="data_labels"> - <pbundle_as_map name="data_shared"> +<pbundle_as_map name="data_shared"> <pbundle_as_map name="location"> <pbundle_as_map name="approx_location"> <int-array name="purposes" num="1"> |